databased.dbmanager
1import argparse 2import shlex 3import sys 4from pathlib import Path 5 6from databased import DataBased, data_to_string 7 8""" A command line tool to interact with a database file. 9Works like a standard argparse based cli 10except it will ask you for arguments indefinitely in a loop 11instead of having to invoke the script with arguments over and over. 12I.e. instead of "python dbManager.py -db database.db -f someString -t someTable", 13just invoke "python dbManager.py" then a repeating "Enter command: " prompt will appear 14and "-db database.db -f someString -t someTable" can be entered. 15Note: a -db arg only needs to be provided once (if there is no default set) unless 16you wish to change databases. So a subsequent command to the above 17can just be entered as "-f someOtherString -t someTable". 18 19This is just a quick template and can be customized by adding arguments and adding/overriding functions 20for specific database projects.""" 21 22# Subclassing to prevent program exit when the -h/--help arg is passed. 23class ArgParser(argparse.ArgumentParser): 24 def exit(self, status=0, message=None): 25 if message: 26 self._print_message(message, sys.stderr) 27 28 29def get_args(command: str) -> argparse.Namespace: 30 parser = ArgParser() 31 32 parser.add_argument( 33 "-db", 34 "--dbname", 35 type=str, 36 default=None, 37 help="""Name of database file to use. 38 Required on the first loop if no default is set, 39 but subsequent loops will resuse the same database 40 unless a new one is provided through this arg.""", 41 ) 42 43 parser.add_argument( 44 "-i", 45 "--info", 46 action="store_true", 47 help=""" Display table names, their respective columns, and how many records they contain. 48 If a -t/--tables arg is passed, just the columns and row count for those tables will be shown.""", 49 ) 50 51 parser.add_argument( 52 "-t", 53 "--tables", 54 type=str, 55 nargs="*", 56 default=[], 57 help="""Limits commands to a specific list of tables. 58 Optional for some commands, required for others. 59 If this is the only arg given (besides -db if not already set), 60 the whole table will be printed to the terminal.""", 61 ) 62 63 parser.add_argument( 64 "-c", 65 "--columns", 66 type=str, 67 nargs="*", 68 default=[], 69 help=""" Limits commands to a specific list of columns. 70 Optional for some commands, required for others. 71 If this and -t are the only args given 72 (besides -db if not already set), the whole table will be printed 73 to the terminal, but with only the columns provided with this arg.""", 74 ) 75 76 parser.add_argument( 77 "-f", 78 "--find", 79 type=str, 80 default=None, 81 help=""" A substring to search the database for. 82 If a -c/--columns arg(s) is not given, the values will be matched against all columns. 83 Similarly, if a -t/--tables arg(s) is not given, the values will be searched for in all tables.""", 84 ) 85 86 parser.add_argument( 87 "-sco", 88 "--show_count_only", 89 action="store_true", 90 help=""" Show the number of results returned by -f/--find, 91 but don't print the results to the terminal.""", 92 ) 93 94 parser.add_argument( 95 "-d", 96 "--delete", 97 type=str, 98 nargs="*", 99 default=[], 100 help=""" A list of values to be deleted from the database. 101 A -c/--columns arg must be supplied. 102 A -t/--tables arg must be supplied.""", 103 ) 104 105 parser.add_argument( 106 "-u", 107 "--update", 108 type=str, 109 default=None, 110 nargs="*", 111 help=""" Update a record in the database. 112 Expects the first argument to be the new value and interprets 113 subsequent arguements as pairs of 'column' and 'value' to use 114 when selecting which rows to update. The -c/--columns arg will 115 be the column that is updated with the new value for matching rows. 116 A -c/--columns arg must be supplied. 117 A -t/--tables arg must be supplied. 118 e.g '-t birds -c last_seen -u today name sparrow migratory 0' 119 will update the 'last_seen' column of the 'birds' table to 'today' 120 for all rows that have either/both of their 'name' and 'migratory' 121 columns set to 'sparrow' and '0', respectively.""", 122 ) 123 124 parser.add_argument( 125 "-sb", "--sort_by", type=str, default=None, help="Column to sort results by." 126 ) 127 128 parser.add_argument( 129 "-q", 130 "--query", 131 type=str, 132 default=None, 133 help=""" Directly execute a query against the database. """, 134 ) 135 136 args = parser.parse_args(command) 137 138 if args.dbname and not Path(args.dbname).exists(): 139 raise Exception(f"{args.dbname} does not exist.") 140 141 return args 142 143 144def info(): 145 print("Getting database info...") 146 print() 147 if not args.tables: 148 args.tables = db.get_table_names() 149 results = [] 150 for table in args.tables: 151 count = db.count(table) 152 columns = db.get_column_names(table) 153 results.append( 154 { 155 "table name": table, 156 "columns": ", ".join(columns), 157 "number of rows": count, 158 } 159 ) 160 if args.sort_by and args.sort_by in results[0]: 161 results = sorted(results, key=lambda x: x[args.sort_by]) 162 print(data_to_string(results)) 163 164 165def find(): 166 print("Finding records... ") 167 print() 168 if not args.tables: 169 args.tables = db.get_table_names() 170 for table in args.tables: 171 results = db.find(table, args.find, args.columns) 172 if args.sort_by and args.sort_by in results[0]: 173 results = sorted(results, key=lambda x: x[args.sort_by]) 174 if args.columns: 175 print( 176 f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:" 177 ) 178 else: 179 print(f"{len(results)} results for '{args.find}' in '{table}' table:") 180 if not args.show_count_only: 181 try: 182 print(data_to_string(results)) 183 except Exception as e: 184 print("Couldn't fit data into a grid.") 185 print(*results, sep="\n") 186 print() 187 188 189def delete(): 190 if not args.tables: 191 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 192 if not args.columns: 193 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 194 print("Deleting records... ") 195 print() 196 num_deleted_records = 0 197 failed_deletions = [] 198 for item in args.delete: 199 success = db.delete(args.tables[0], [(args.columns[0], item)]) 200 if success: 201 num_deleted_records += success 202 else: 203 failed_deletions.append(item) 204 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 205 if len(failed_deletions) > 0: 206 print( 207 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 208 ) 209 for fail in failed_deletions: 210 print(f" {fail}") 211 212 213def update(): 214 if not args.tables: 215 raise ValueError("Missing -t/--tables arg for -u/--update function.") 216 if not args.columns: 217 raise ValueError("Missing -c/--columns arg for -u/--update function.") 218 print("Updating record... ") 219 print() 220 new_value = args.update[0] 221 if len(args.update) > 1: 222 args.update = args.update[1:] 223 match_criteria = [ 224 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 225 ] 226 else: 227 match_criteria = None 228 if db.update( 229 args.tables[0], 230 args.columns[0], 231 new_value, 232 match_criteria, 233 ): 234 print( 235 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 236 ) 237 else: 238 print( 239 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 240 ) 241 242 243def print_table(): 244 for table in args.tables: 245 rows = db.get_rows( 246 table, columns_to_return=args.columns, sort_by_column=args.sort_by 247 ) 248 print(f"{table} table:") 249 print(data_to_string(rows)) 250 251 252def query(): 253 results = db.query(args.query) 254 try: 255 for result in results: 256 print(*result, sep=" * ") 257 except Exception as e: 258 print(f"Couldn't display results of '{args.query}'.") 259 260 261if __name__ == "__main__": 262 sys.tracebacklimit = 0 263 dbname = "$dbname" 264 while True: 265 try: 266 command = shlex.split(input("Enter command: ")) 267 args = get_args(command) 268 if args.dbname: 269 dbname = args.dbname 270 with DataBased(dbpath=dbname) as db: 271 if args.info: 272 info() 273 elif args.find: 274 find() 275 elif args.delete: 276 delete() 277 elif args.update: 278 update() 279 elif args.query: 280 query() 281 else: 282 print_table() 283 except KeyboardInterrupt: 284 break 285 except Exception as e: 286 print(e)
24class ArgParser(argparse.ArgumentParser): 25 def exit(self, status=0, message=None): 26 if message: 27 self._print_message(message, sys.stderr)
Object for parsing command line strings into Python objects.
Keyword Arguments:
- prog -- The name of the program (default:
os.path.basename(sys.argv[0])
)
- usage -- A usage message (default: auto-generated from arguments)
- description -- A description of what the program does
- epilog -- Text following the argument descriptions
- parents -- Parsers whose arguments should be copied into this one
- formatter_class -- HelpFormatter class for printing help messages
- prefix_chars -- Characters that prefix optional arguments
- fromfile_prefix_chars -- Characters that prefix files containing
additional arguments
- argument_default -- The default value for all arguments
- conflict_handler -- String indicating how to handle conflicts
- add_help -- Add a -h/-help option
- allow_abbrev -- Allow long options to be abbreviated unambiguously
- exit_on_error -- Determines whether or not ArgumentParser exits with
error info when an error occurs
Inherited Members
- argparse.ArgumentParser
- ArgumentParser
- add_subparsers
- parse_args
- parse_known_args
- convert_arg_line_to_args
- parse_intermixed_args
- parse_known_intermixed_args
- format_usage
- format_help
- print_usage
- print_help
- error
- argparse._ActionsContainer
- register
- set_defaults
- get_default
- add_argument
- add_argument_group
- add_mutually_exclusive_group
30def get_args(command: str) -> argparse.Namespace: 31 parser = ArgParser() 32 33 parser.add_argument( 34 "-db", 35 "--dbname", 36 type=str, 37 default=None, 38 help="""Name of database file to use. 39 Required on the first loop if no default is set, 40 but subsequent loops will resuse the same database 41 unless a new one is provided through this arg.""", 42 ) 43 44 parser.add_argument( 45 "-i", 46 "--info", 47 action="store_true", 48 help=""" Display table names, their respective columns, and how many records they contain. 49 If a -t/--tables arg is passed, just the columns and row count for those tables will be shown.""", 50 ) 51 52 parser.add_argument( 53 "-t", 54 "--tables", 55 type=str, 56 nargs="*", 57 default=[], 58 help="""Limits commands to a specific list of tables. 59 Optional for some commands, required for others. 60 If this is the only arg given (besides -db if not already set), 61 the whole table will be printed to the terminal.""", 62 ) 63 64 parser.add_argument( 65 "-c", 66 "--columns", 67 type=str, 68 nargs="*", 69 default=[], 70 help=""" Limits commands to a specific list of columns. 71 Optional for some commands, required for others. 72 If this and -t are the only args given 73 (besides -db if not already set), the whole table will be printed 74 to the terminal, but with only the columns provided with this arg.""", 75 ) 76 77 parser.add_argument( 78 "-f", 79 "--find", 80 type=str, 81 default=None, 82 help=""" A substring to search the database for. 83 If a -c/--columns arg(s) is not given, the values will be matched against all columns. 84 Similarly, if a -t/--tables arg(s) is not given, the values will be searched for in all tables.""", 85 ) 86 87 parser.add_argument( 88 "-sco", 89 "--show_count_only", 90 action="store_true", 91 help=""" Show the number of results returned by -f/--find, 92 but don't print the results to the terminal.""", 93 ) 94 95 parser.add_argument( 96 "-d", 97 "--delete", 98 type=str, 99 nargs="*", 100 default=[], 101 help=""" A list of values to be deleted from the database. 102 A -c/--columns arg must be supplied. 103 A -t/--tables arg must be supplied.""", 104 ) 105 106 parser.add_argument( 107 "-u", 108 "--update", 109 type=str, 110 default=None, 111 nargs="*", 112 help=""" Update a record in the database. 113 Expects the first argument to be the new value and interprets 114 subsequent arguements as pairs of 'column' and 'value' to use 115 when selecting which rows to update. The -c/--columns arg will 116 be the column that is updated with the new value for matching rows. 117 A -c/--columns arg must be supplied. 118 A -t/--tables arg must be supplied. 119 e.g '-t birds -c last_seen -u today name sparrow migratory 0' 120 will update the 'last_seen' column of the 'birds' table to 'today' 121 for all rows that have either/both of their 'name' and 'migratory' 122 columns set to 'sparrow' and '0', respectively.""", 123 ) 124 125 parser.add_argument( 126 "-sb", "--sort_by", type=str, default=None, help="Column to sort results by." 127 ) 128 129 parser.add_argument( 130 "-q", 131 "--query", 132 type=str, 133 default=None, 134 help=""" Directly execute a query against the database. """, 135 ) 136 137 args = parser.parse_args(command) 138 139 if args.dbname and not Path(args.dbname).exists(): 140 raise Exception(f"{args.dbname} does not exist.") 141 142 return args
145def info(): 146 print("Getting database info...") 147 print() 148 if not args.tables: 149 args.tables = db.get_table_names() 150 results = [] 151 for table in args.tables: 152 count = db.count(table) 153 columns = db.get_column_names(table) 154 results.append( 155 { 156 "table name": table, 157 "columns": ", ".join(columns), 158 "number of rows": count, 159 } 160 ) 161 if args.sort_by and args.sort_by in results[0]: 162 results = sorted(results, key=lambda x: x[args.sort_by]) 163 print(data_to_string(results))
166def find(): 167 print("Finding records... ") 168 print() 169 if not args.tables: 170 args.tables = db.get_table_names() 171 for table in args.tables: 172 results = db.find(table, args.find, args.columns) 173 if args.sort_by and args.sort_by in results[0]: 174 results = sorted(results, key=lambda x: x[args.sort_by]) 175 if args.columns: 176 print( 177 f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:" 178 ) 179 else: 180 print(f"{len(results)} results for '{args.find}' in '{table}' table:") 181 if not args.show_count_only: 182 try: 183 print(data_to_string(results)) 184 except Exception as e: 185 print("Couldn't fit data into a grid.") 186 print(*results, sep="\n") 187 print()
190def delete(): 191 if not args.tables: 192 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 193 if not args.columns: 194 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 195 print("Deleting records... ") 196 print() 197 num_deleted_records = 0 198 failed_deletions = [] 199 for item in args.delete: 200 success = db.delete(args.tables[0], [(args.columns[0], item)]) 201 if success: 202 num_deleted_records += success 203 else: 204 failed_deletions.append(item) 205 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 206 if len(failed_deletions) > 0: 207 print( 208 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 209 ) 210 for fail in failed_deletions: 211 print(f" {fail}")
214def update(): 215 if not args.tables: 216 raise ValueError("Missing -t/--tables arg for -u/--update function.") 217 if not args.columns: 218 raise ValueError("Missing -c/--columns arg for -u/--update function.") 219 print("Updating record... ") 220 print() 221 new_value = args.update[0] 222 if len(args.update) > 1: 223 args.update = args.update[1:] 224 match_criteria = [ 225 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 226 ] 227 else: 228 match_criteria = None 229 if db.update( 230 args.tables[0], 231 args.columns[0], 232 new_value, 233 match_criteria, 234 ): 235 print( 236 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 237 ) 238 else: 239 print( 240 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 241 )