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 args = parser.parse_args(command) 129 130 if args.dbname and not Path(args.dbname).exists(): 131 raise Exception(f"{args.dbname} does not exist.") 132 133 return args 134 135 136def info(): 137 print("Getting database info...") 138 print() 139 if not args.tables: 140 args.tables = db.get_table_names() 141 results = [] 142 for table in args.tables: 143 count = db.count(table) 144 columns = db.get_column_names(table) 145 results.append( 146 { 147 "table name": table, 148 "columns": ", ".join(columns), 149 "number of rows": count, 150 } 151 ) 152 if args.sort_by and args.sort_by in results[0]: 153 results = sorted(results, key=lambda x: x[args.sort_by]) 154 print(data_to_string(results)) 155 156 157def find(): 158 print("Finding records... ") 159 print() 160 if not args.tables: 161 args.tables = db.get_table_names() 162 for table in args.tables: 163 results = db.find(table, args.find, args.columns) 164 if args.sort_by and args.sort_by in results[0]: 165 results = sorted(results, key=lambda x: x[args.sort_by]) 166 if args.columns: 167 print( 168 f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:" 169 ) 170 else: 171 print(f"{len(results)} results for '{args.find}' in '{table}' table:") 172 if not args.show_count_only: 173 print(data_to_string(results)) 174 print() 175 176 177def delete(): 178 if not args.tables: 179 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 180 if not args.columns: 181 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 182 print("Deleting records... ") 183 print() 184 num_deleted_records = 0 185 failed_deletions = [] 186 for item in args.delete: 187 success = db.delete(args.tables[0], [(args.columns[0], item)]) 188 if success: 189 num_deleted_records += success 190 else: 191 failed_deletions.append(item) 192 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 193 if len(failed_deletions) > 0: 194 print( 195 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 196 ) 197 for fail in failed_deletions: 198 print(f" {fail}") 199 200 201def update(): 202 if not args.tables: 203 raise ValueError("Missing -t/--tables arg for -u/--update function.") 204 if not args.columns: 205 raise ValueError("Missing -c/--columns arg for -u/--update function.") 206 print("Updating record... ") 207 print() 208 new_value = args.update[0] 209 if len(args.update) > 1: 210 args.update = args.update[1:] 211 match_criteria = [ 212 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 213 ] 214 else: 215 match_criteria = None 216 if db.update( 217 args.tables[0], 218 args.columns[0], 219 new_value, 220 match_criteria, 221 ): 222 print( 223 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 224 ) 225 else: 226 print( 227 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 228 ) 229 230 231def print_table(): 232 for table in args.tables: 233 rows = db.get_rows( 234 table, columns_to_return=args.columns, sort_by_column=args.sort_by 235 ) 236 print(f"{table} table:") 237 print(data_to_string(rows)) 238 239 240if __name__ == "__main__": 241 sys.tracebacklimit = 0 242 while True: 243 try: 244 command = shlex.split(input("Enter command: ")) 245 args = get_args(command) 246 if args.dbname: 247 dbname = args.dbname 248 with DataBased(dbpath=dbname) as db: 249 if args.info: 250 info() 251 elif args.find: 252 find() 253 elif args.delete: 254 delete() 255 elif args.update: 256 update() 257 else: 258 print_table() 259 except KeyboardInterrupt: 260 break 261 except Exception as e: 262 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 args = parser.parse_args(command) 130 131 if args.dbname and not Path(args.dbname).exists(): 132 raise Exception(f"{args.dbname} does not exist.") 133 134 return args
137def info(): 138 print("Getting database info...") 139 print() 140 if not args.tables: 141 args.tables = db.get_table_names() 142 results = [] 143 for table in args.tables: 144 count = db.count(table) 145 columns = db.get_column_names(table) 146 results.append( 147 { 148 "table name": table, 149 "columns": ", ".join(columns), 150 "number of rows": count, 151 } 152 ) 153 if args.sort_by and args.sort_by in results[0]: 154 results = sorted(results, key=lambda x: x[args.sort_by]) 155 print(data_to_string(results))
158def find(): 159 print("Finding records... ") 160 print() 161 if not args.tables: 162 args.tables = db.get_table_names() 163 for table in args.tables: 164 results = db.find(table, args.find, args.columns) 165 if args.sort_by and args.sort_by in results[0]: 166 results = sorted(results, key=lambda x: x[args.sort_by]) 167 if args.columns: 168 print( 169 f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:" 170 ) 171 else: 172 print(f"{len(results)} results for '{args.find}' in '{table}' table:") 173 if not args.show_count_only: 174 print(data_to_string(results)) 175 print()
178def delete(): 179 if not args.tables: 180 raise ValueError("Missing -t/--tables arg for -d/--delete function.") 181 if not args.columns: 182 raise ValueError("Missing -c/--columns arg for -d/--delete function.") 183 print("Deleting records... ") 184 print() 185 num_deleted_records = 0 186 failed_deletions = [] 187 for item in args.delete: 188 success = db.delete(args.tables[0], [(args.columns[0], item)]) 189 if success: 190 num_deleted_records += success 191 else: 192 failed_deletions.append(item) 193 print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.") 194 if len(failed_deletions) > 0: 195 print( 196 f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:" 197 ) 198 for fail in failed_deletions: 199 print(f" {fail}")
202def update(): 203 if not args.tables: 204 raise ValueError("Missing -t/--tables arg for -u/--update function.") 205 if not args.columns: 206 raise ValueError("Missing -c/--columns arg for -u/--update function.") 207 print("Updating record... ") 208 print() 209 new_value = args.update[0] 210 if len(args.update) > 1: 211 args.update = args.update[1:] 212 match_criteria = [ 213 (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2) 214 ] 215 else: 216 match_criteria = None 217 if db.update( 218 args.tables[0], 219 args.columns[0], 220 new_value, 221 match_criteria, 222 ): 223 print( 224 f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 225 ) 226 else: 227 print( 228 f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}." 229 )