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 print(data_to_string(results)) 182 print() 183 184 185def delete(): 186 if not args.tables: 187 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 188 if not args.columns: 189 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 190 print("Deleting records... ") 191 print() 192 num_deleted_records = 0 193 failed_deletions = [] 194 for item in args.delete: 195 success = db.delete(args.tables[0], [(args.columns[0], item)]) 196 if success: 197 num_deleted_records += success 198 else: 199 failed_deletions.append(item) 200 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 201 if len(failed_deletions) > 0: 202 print( 203 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 204 ) 205 for fail in failed_deletions: 206 print(f" {fail}") 207 208 209def update(): 210 if not args.tables: 211 raise ValueError("Missing -t/--tables arg for -u/--update function.") 212 if not args.columns: 213 raise ValueError("Missing -c/--columns arg for -u/--update function.") 214 print("Updating record... ") 215 print() 216 new_value = args.update[0] 217 if len(args.update) > 1: 218 args.update = args.update[1:] 219 match_criteria = [ 220 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 221 ] 222 else: 223 match_criteria = None 224 if db.update( 225 args.tables[0], 226 args.columns[0], 227 new_value, 228 match_criteria, 229 ): 230 print( 231 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 232 ) 233 else: 234 print( 235 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 236 ) 237 238 239def print_table(): 240 for table in args.tables: 241 rows = db.get_rows( 242 table, columns_to_return=args.columns, sort_by_column=args.sort_by 243 ) 244 print(f"{table} table:") 245 print(data_to_string(rows)) 246 247 248def query(): 249 results = db.query(args.query) 250 try: 251 for result in results: 252 print(*result, sep=" * ") 253 except Exception as e: 254 print(f"Couldn't display results of '{args.query}'.") 255 256 257if __name__ == "__main__": 258 sys.tracebacklimit = 0 259 while True: 260 try: 261 command = shlex.split(input("Enter command: ")) 262 args = get_args(command) 263 if args.dbname: 264 dbname = args.dbname 265 with DataBased(dbpath=dbname) as db: 266 if args.info: 267 info() 268 elif args.find: 269 find() 270 elif args.delete: 271 delete() 272 elif args.update: 273 update() 274 elif args.query: 275 query() 276 else: 277 print_table() 278 except KeyboardInterrupt: 279 break 280 except Exception as e: 281 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 print(data_to_string(results)) 183 print()
186def delete(): 187 if not args.tables: 188 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 189 if not args.columns: 190 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 191 print("Deleting records... ") 192 print() 193 num_deleted_records = 0 194 failed_deletions = [] 195 for item in args.delete: 196 success = db.delete(args.tables[0], [(args.columns[0], item)]) 197 if success: 198 num_deleted_records += success 199 else: 200 failed_deletions.append(item) 201 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 202 if len(failed_deletions) > 0: 203 print( 204 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 205 ) 206 for fail in failed_deletions: 207 print(f" {fail}")
210def update(): 211 if not args.tables: 212 raise ValueError("Missing -t/--tables arg for -u/--update function.") 213 if not args.columns: 214 raise ValueError("Missing -c/--columns arg for -u/--update function.") 215 print("Updating record... ") 216 print() 217 new_value = args.update[0] 218 if len(args.update) > 1: 219 args.update = args.update[1:] 220 match_criteria = [ 221 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 222 ] 223 else: 224 match_criteria = None 225 if db.update( 226 args.tables[0], 227 args.columns[0], 228 new_value, 229 match_criteria, 230 ): 231 print( 232 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 233 ) 234 else: 235 print( 236 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 237 )