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