databased.dbshell
1import argshell 2from griddle import griddy 3from pathier import Pathier 4 5from databased import DataBased, create_shell, dbparsers 6 7 8class DBShell(argshell.ArgShell): 9 intro = "Starting dbshell (enter help or ? for arg info)..." 10 prompt = "based>" 11 dbpath: Pathier = None # type: ignore 12 13 def do_use_db(self, arg: str): 14 """Set which database file to use.""" 15 dbpath = Pathier(arg) 16 if not dbpath.exists(): 17 print(f"{dbpath} does not exist.") 18 print(f"Still using {self.dbpath}") 19 elif not dbpath.is_file(): 20 print(f"{dbpath} is not a file.") 21 print(f"Still using {self.dbpath}") 22 else: 23 self.dbpath = dbpath 24 25 def do_dbpath(self, arg: str): 26 """Print the .db file in use.""" 27 print(self.dbpath) 28 29 @argshell.with_parser(dbparsers.get_backup_parser) 30 def do_backup(self, args: argshell.Namespace): 31 """Create a backup of the current db file.""" 32 print(f"Creating a back up for {self.dbpath}...") 33 backup_path = self.dbpath.backup(args.timestamp) 34 print("Creating backup is complete.") 35 print(f"Backup path: {backup_path}") 36 37 def do_size(self, arg: str): 38 """Display the size of the the current db file.""" 39 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 40 41 @argshell.with_parser(dbparsers.get_create_table_parser) 42 def do_add_table(self, args: argshell.Namespace): 43 """Add a new table to the database.""" 44 with DataBased(self.dbpath) as db: 45 db.create_table(args.table_name, args.columns) 46 47 def do_drop_table(self, arg: str): 48 """Drop the specified table.""" 49 with DataBased(self.dbpath) as db: 50 db.drop_table(arg) 51 52 @argshell.with_parser( 53 dbparsers.get_add_row_parser, [dbparsers.verify_matching_length] 54 ) 55 def do_add_row(self, args: argshell.Namespace): 56 """Add a row to a table.""" 57 with DataBased(self.dbpath) as db: 58 if db.add_row(args.table_name, args.values, args.columns or None): 59 print(f"Added row to {args.table_name} table successfully.") 60 else: 61 print(f"Failed to add row to {args.table_name} table.") 62 63 @argshell.with_parser(dbparsers.get_info_parser) 64 def do_info(self, args: argshell.Namespace): 65 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 66 print("Getting database info...") 67 with DataBased(self.dbpath) as db: 68 tables = args.tables or db.get_table_names() 69 info = [ 70 { 71 "Table Name": table, 72 "Columns": ", ".join(db.get_column_names(table)), 73 "Number of Rows": db.count(table) if args.rowcount else "n/a", 74 } 75 for table in tables 76 ] 77 print(DataBased.data_to_string(info)) 78 79 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 80 def do_show(self, args: argshell.Namespace): 81 """Find and print rows from the database. 82 Use the -t/--tables, -m/--match_pairs, and -l/--limit flags to limit the search. 83 Use the -c/--columns flag to limit what columns are printed. 84 Use the -o/--order_by flag to order the results. 85 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs 86 Pass -h/--help flag for parser help.""" 87 print("Finding records... ") 88 if len(args.columns) == 0: 89 args.columns = None 90 with DataBased(self.dbpath) as db: 91 tables = args.tables or db.get_table_names() 92 for table in tables: 93 results = db.get_rows( 94 table, 95 args.match_pairs, 96 columns_to_return=args.columns, 97 order_by=args.order_by, 98 limit=args.limit, 99 exact_match=not args.partial_matching, 100 ) 101 db.close() 102 print(f"{len(results)} matching rows in {table} table.") 103 try: 104 print(DataBased.data_to_string(results)) # type: ignore 105 except Exception as e: 106 print("Couldn't fit data into a grid.") 107 print(*results, sep="\n") 108 if results: 109 print(f"{len(results)} matching rows in {table} table.") 110 print() 111 112 @argshell.with_parser(dbparsers.get_search_parser) 113 def do_search(self, args: argshell.Namespace): 114 """Search and return any rows containg the searched substring in any of its columns. 115 Use the -t/--tables flag to limit the search to a specific table(s). 116 Use the -c/--columns flag to limit the search to a specific column(s).""" 117 print(f"Searching for {args.search_string}...") 118 with DataBased(self.dbpath) as db: 119 tables = args.tables or db.get_table_names() 120 for table in tables: 121 columns = args.columns or db.get_column_names(table) 122 matcher = " OR ".join( 123 f'{column} LIKE "%{args.search_string}%"' for column in columns 124 ) 125 query = f"SELECT * FROM {table} WHERE {matcher};" 126 results = db.query(query) 127 results = [db._get_dict(table, result) for result in results] 128 print(f"Found {len(results)} results in {table} table.") 129 print(DataBased.data_to_string(results)) 130 131 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 132 def do_count(self, args: argshell.Namespace): 133 """Print the number of rows in the database. 134 Use the -t/--tables flag to limit results to a specific table(s). 135 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 136 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 137 Pass -h/--help flag for parser help.""" 138 print("Counting rows...") 139 with DataBased(self.dbpath) as db: 140 tables = args.tables or db.get_table_names() 141 for table in tables: 142 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 143 print(f"{num_rows} matching rows in {table} table.") 144 145 def do_query(self, arg: str): 146 """Execute a query against the current database.""" 147 print(f"Executing {arg}") 148 with DataBased(self.dbpath) as db: 149 results = db.query(arg) 150 try: 151 try: 152 print(griddy(results)) 153 except Exception as e: 154 for result in results: 155 print(*result, sep="|-|") 156 except Exception as e: 157 print(f"{type(e).__name__}: {e}") 158 print(f"{db.cursor.rowcount} affected rows") 159 160 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 161 def do_update(self, args: argshell.Namespace): 162 """Update a column to a new value. 163 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 164 Use the -t/--tables flag to limit what tables are updated. 165 Use the -m/--match_pairs flag to specify which rows are updated. 166 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 167 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 168 169 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 170 print("Updating rows...") 171 with DataBased(self.dbpath) as db: 172 tables = args.tables or db.get_table_names() 173 for table in tables: 174 num_updates = db.update( 175 table, 176 args.column, 177 args.new_value, 178 args.match_pairs, 179 not args.partial_matching, 180 ) 181 print(f"Updated {num_updates} rows in {table} table.") 182 183 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 184 def do_delete(self, args: argshell.Namespace): 185 """Delete rows from the database. 186 Use the -t/--tables flag to limit what tables rows are deleted from. 187 Use the -m/--match_pairs flag to specify which rows are deleted. 188 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 189 >>> based>delete -t users -m username chungus -p 190 191 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 192 print("Deleting records...") 193 with DataBased(self.dbpath) as db: 194 tables = args.tables or db.get_table_names() 195 for table in tables: 196 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 197 print(f"Deleted {num_rows} rows from {table} table.") 198 199 @argshell.with_parser(dbparsers.get_add_column_parser) 200 def do_add_column(self, args: argshell.Namespace): 201 """Add a new column to the specified tables.""" 202 with DataBased(self.dbpath) as db: 203 tables = args.tables or db.get_table_names() 204 for table in tables: 205 db.add_column(table, args.column_name, args.type, args.default_value) 206 207 def do_flush_log(self, arg: str): 208 """Clear the log file for this database.""" 209 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 210 if not log_path.exists(): 211 print(f"No log file at path {log_path}") 212 else: 213 print(f"Flushing log...") 214 log_path.write_text("") 215 216 def do_scan_dbs(self, arg: str): 217 """Scan the current working directory for `*.db` files and display them. 218 219 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 220 cwd = Pathier.cwd() 221 if arg.strip() == "r": 222 dbs = cwd.rglob("*.db") 223 else: 224 dbs = cwd.glob("*.db") 225 for db in dbs: 226 print(db.separate(cwd.stem)) 227 228 def do_customize(self, arg: str): 229 """Generate a template file in the current working directory for creating a custom DBShell class. 230 Expects one argument: the name of the custom dbshell. 231 This will be used to name the generated file as well as several components in the file content.""" 232 try: 233 create_shell(arg) 234 except Exception as e: 235 print(f"{type(e).__name__}: {e}") 236 237 def do_vacuum(self, arg: str): 238 """Reduce database disk memory.""" 239 starting_size = self.dbpath.size 240 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 241 print("Vacuuming database...") 242 with DataBased(self.dbpath) as db: 243 freedspace = db.vacuum() 244 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 245 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.") 246 247 def _choose_db(self, options: list[Pathier]) -> Pathier: 248 """Prompt the user to select from a list of files.""" 249 cwd = Pathier.cwd() 250 paths = [path.separate(cwd.stem) for path in options] 251 while True: 252 print( 253 f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}" 254 ) 255 choice = input("Enter the number of the option to use: ") 256 try: 257 index = int(choice) 258 if not 1 <= index <= len(options): 259 print("Choice out of range.") 260 continue 261 return options[index - 1] 262 except Exception as e: 263 print(f"{choice} is not a valid option.") 264 265 def preloop(self): 266 """Scan the current directory for a .db file to use. 267 If not found, prompt the user for one or to try again recursively.""" 268 if self.dbpath: 269 self.dbpath = Pathier(self.dbpath) 270 print(f"Defaulting to database {self.dbpath}") 271 else: 272 print("Searching for database...") 273 cwd = Pathier.cwd() 274 dbs = list(cwd.glob("*.db")) 275 if len(dbs) == 1: 276 self.dbpath = dbs[0] 277 print(f"Using database {self.dbpath}.") 278 elif dbs: 279 self.dbpath = self._choose_db(dbs) 280 else: 281 print(f"Could not find a .db file in {cwd}.") 282 path = input( 283 "Enter path to .db file to use or press enter to search again recursively: " 284 ) 285 if path: 286 self.dbpath = Pathier(path) 287 elif not path: 288 print("Searching recursively...") 289 dbs = list(cwd.rglob("*.db")) 290 if len(dbs) == 1: 291 self.dbpath = dbs[0] 292 print(f"Using database {self.dbpath}.") 293 elif dbs: 294 self.dbpath = self._choose_db(dbs) 295 else: 296 print("Could not find a .db file.") 297 self.dbpath = Pathier(input("Enter path to a .db file: ")) 298 if not self.dbpath.exists(): 299 raise FileNotFoundError(f"{self.dbpath} does not exist.") 300 if not self.dbpath.is_file(): 301 raise ValueError(f"{self.dbpath} is not a file.") 302 303 304def main(): 305 DBShell().cmdloop()
9class DBShell(argshell.ArgShell): 10 intro = "Starting dbshell (enter help or ? for arg info)..." 11 prompt = "based>" 12 dbpath: Pathier = None # type: ignore 13 14 def do_use_db(self, arg: str): 15 """Set which database file to use.""" 16 dbpath = Pathier(arg) 17 if not dbpath.exists(): 18 print(f"{dbpath} does not exist.") 19 print(f"Still using {self.dbpath}") 20 elif not dbpath.is_file(): 21 print(f"{dbpath} is not a file.") 22 print(f"Still using {self.dbpath}") 23 else: 24 self.dbpath = dbpath 25 26 def do_dbpath(self, arg: str): 27 """Print the .db file in use.""" 28 print(self.dbpath) 29 30 @argshell.with_parser(dbparsers.get_backup_parser) 31 def do_backup(self, args: argshell.Namespace): 32 """Create a backup of the current db file.""" 33 print(f"Creating a back up for {self.dbpath}...") 34 backup_path = self.dbpath.backup(args.timestamp) 35 print("Creating backup is complete.") 36 print(f"Backup path: {backup_path}") 37 38 def do_size(self, arg: str): 39 """Display the size of the the current db file.""" 40 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 41 42 @argshell.with_parser(dbparsers.get_create_table_parser) 43 def do_add_table(self, args: argshell.Namespace): 44 """Add a new table to the database.""" 45 with DataBased(self.dbpath) as db: 46 db.create_table(args.table_name, args.columns) 47 48 def do_drop_table(self, arg: str): 49 """Drop the specified table.""" 50 with DataBased(self.dbpath) as db: 51 db.drop_table(arg) 52 53 @argshell.with_parser( 54 dbparsers.get_add_row_parser, [dbparsers.verify_matching_length] 55 ) 56 def do_add_row(self, args: argshell.Namespace): 57 """Add a row to a table.""" 58 with DataBased(self.dbpath) as db: 59 if db.add_row(args.table_name, args.values, args.columns or None): 60 print(f"Added row to {args.table_name} table successfully.") 61 else: 62 print(f"Failed to add row to {args.table_name} table.") 63 64 @argshell.with_parser(dbparsers.get_info_parser) 65 def do_info(self, args: argshell.Namespace): 66 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 67 print("Getting database info...") 68 with DataBased(self.dbpath) as db: 69 tables = args.tables or db.get_table_names() 70 info = [ 71 { 72 "Table Name": table, 73 "Columns": ", ".join(db.get_column_names(table)), 74 "Number of Rows": db.count(table) if args.rowcount else "n/a", 75 } 76 for table in tables 77 ] 78 print(DataBased.data_to_string(info)) 79 80 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 81 def do_show(self, args: argshell.Namespace): 82 """Find and print rows from the database. 83 Use the -t/--tables, -m/--match_pairs, and -l/--limit flags to limit the search. 84 Use the -c/--columns flag to limit what columns are printed. 85 Use the -o/--order_by flag to order the results. 86 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs 87 Pass -h/--help flag for parser help.""" 88 print("Finding records... ") 89 if len(args.columns) == 0: 90 args.columns = None 91 with DataBased(self.dbpath) as db: 92 tables = args.tables or db.get_table_names() 93 for table in tables: 94 results = db.get_rows( 95 table, 96 args.match_pairs, 97 columns_to_return=args.columns, 98 order_by=args.order_by, 99 limit=args.limit, 100 exact_match=not args.partial_matching, 101 ) 102 db.close() 103 print(f"{len(results)} matching rows in {table} table.") 104 try: 105 print(DataBased.data_to_string(results)) # type: ignore 106 except Exception as e: 107 print("Couldn't fit data into a grid.") 108 print(*results, sep="\n") 109 if results: 110 print(f"{len(results)} matching rows in {table} table.") 111 print() 112 113 @argshell.with_parser(dbparsers.get_search_parser) 114 def do_search(self, args: argshell.Namespace): 115 """Search and return any rows containg the searched substring in any of its columns. 116 Use the -t/--tables flag to limit the search to a specific table(s). 117 Use the -c/--columns flag to limit the search to a specific column(s).""" 118 print(f"Searching for {args.search_string}...") 119 with DataBased(self.dbpath) as db: 120 tables = args.tables or db.get_table_names() 121 for table in tables: 122 columns = args.columns or db.get_column_names(table) 123 matcher = " OR ".join( 124 f'{column} LIKE "%{args.search_string}%"' for column in columns 125 ) 126 query = f"SELECT * FROM {table} WHERE {matcher};" 127 results = db.query(query) 128 results = [db._get_dict(table, result) for result in results] 129 print(f"Found {len(results)} results in {table} table.") 130 print(DataBased.data_to_string(results)) 131 132 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 133 def do_count(self, args: argshell.Namespace): 134 """Print the number of rows in the database. 135 Use the -t/--tables flag to limit results to a specific table(s). 136 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 137 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 138 Pass -h/--help flag for parser help.""" 139 print("Counting rows...") 140 with DataBased(self.dbpath) as db: 141 tables = args.tables or db.get_table_names() 142 for table in tables: 143 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 144 print(f"{num_rows} matching rows in {table} table.") 145 146 def do_query(self, arg: str): 147 """Execute a query against the current database.""" 148 print(f"Executing {arg}") 149 with DataBased(self.dbpath) as db: 150 results = db.query(arg) 151 try: 152 try: 153 print(griddy(results)) 154 except Exception as e: 155 for result in results: 156 print(*result, sep="|-|") 157 except Exception as e: 158 print(f"{type(e).__name__}: {e}") 159 print(f"{db.cursor.rowcount} affected rows") 160 161 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 162 def do_update(self, args: argshell.Namespace): 163 """Update a column to a new value. 164 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 165 Use the -t/--tables flag to limit what tables are updated. 166 Use the -m/--match_pairs flag to specify which rows are updated. 167 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 168 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 169 170 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 171 print("Updating rows...") 172 with DataBased(self.dbpath) as db: 173 tables = args.tables or db.get_table_names() 174 for table in tables: 175 num_updates = db.update( 176 table, 177 args.column, 178 args.new_value, 179 args.match_pairs, 180 not args.partial_matching, 181 ) 182 print(f"Updated {num_updates} rows in {table} table.") 183 184 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 185 def do_delete(self, args: argshell.Namespace): 186 """Delete rows from the database. 187 Use the -t/--tables flag to limit what tables rows are deleted from. 188 Use the -m/--match_pairs flag to specify which rows are deleted. 189 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 190 >>> based>delete -t users -m username chungus -p 191 192 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 193 print("Deleting records...") 194 with DataBased(self.dbpath) as db: 195 tables = args.tables or db.get_table_names() 196 for table in tables: 197 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 198 print(f"Deleted {num_rows} rows from {table} table.") 199 200 @argshell.with_parser(dbparsers.get_add_column_parser) 201 def do_add_column(self, args: argshell.Namespace): 202 """Add a new column to the specified tables.""" 203 with DataBased(self.dbpath) as db: 204 tables = args.tables or db.get_table_names() 205 for table in tables: 206 db.add_column(table, args.column_name, args.type, args.default_value) 207 208 def do_flush_log(self, arg: str): 209 """Clear the log file for this database.""" 210 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 211 if not log_path.exists(): 212 print(f"No log file at path {log_path}") 213 else: 214 print(f"Flushing log...") 215 log_path.write_text("") 216 217 def do_scan_dbs(self, arg: str): 218 """Scan the current working directory for `*.db` files and display them. 219 220 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 221 cwd = Pathier.cwd() 222 if arg.strip() == "r": 223 dbs = cwd.rglob("*.db") 224 else: 225 dbs = cwd.glob("*.db") 226 for db in dbs: 227 print(db.separate(cwd.stem)) 228 229 def do_customize(self, arg: str): 230 """Generate a template file in the current working directory for creating a custom DBShell class. 231 Expects one argument: the name of the custom dbshell. 232 This will be used to name the generated file as well as several components in the file content.""" 233 try: 234 create_shell(arg) 235 except Exception as e: 236 print(f"{type(e).__name__}: {e}") 237 238 def do_vacuum(self, arg: str): 239 """Reduce database disk memory.""" 240 starting_size = self.dbpath.size 241 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 242 print("Vacuuming database...") 243 with DataBased(self.dbpath) as db: 244 freedspace = db.vacuum() 245 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 246 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.") 247 248 def _choose_db(self, options: list[Pathier]) -> Pathier: 249 """Prompt the user to select from a list of files.""" 250 cwd = Pathier.cwd() 251 paths = [path.separate(cwd.stem) for path in options] 252 while True: 253 print( 254 f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}" 255 ) 256 choice = input("Enter the number of the option to use: ") 257 try: 258 index = int(choice) 259 if not 1 <= index <= len(options): 260 print("Choice out of range.") 261 continue 262 return options[index - 1] 263 except Exception as e: 264 print(f"{choice} is not a valid option.") 265 266 def preloop(self): 267 """Scan the current directory for a .db file to use. 268 If not found, prompt the user for one or to try again recursively.""" 269 if self.dbpath: 270 self.dbpath = Pathier(self.dbpath) 271 print(f"Defaulting to database {self.dbpath}") 272 else: 273 print("Searching for database...") 274 cwd = Pathier.cwd() 275 dbs = list(cwd.glob("*.db")) 276 if len(dbs) == 1: 277 self.dbpath = dbs[0] 278 print(f"Using database {self.dbpath}.") 279 elif dbs: 280 self.dbpath = self._choose_db(dbs) 281 else: 282 print(f"Could not find a .db file in {cwd}.") 283 path = input( 284 "Enter path to .db file to use or press enter to search again recursively: " 285 ) 286 if path: 287 self.dbpath = Pathier(path) 288 elif not path: 289 print("Searching recursively...") 290 dbs = list(cwd.rglob("*.db")) 291 if len(dbs) == 1: 292 self.dbpath = dbs[0] 293 print(f"Using database {self.dbpath}.") 294 elif dbs: 295 self.dbpath = self._choose_db(dbs) 296 else: 297 print("Could not find a .db file.") 298 self.dbpath = Pathier(input("Enter path to a .db file: ")) 299 if not self.dbpath.exists(): 300 raise FileNotFoundError(f"{self.dbpath} does not exist.") 301 if not self.dbpath.is_file(): 302 raise ValueError(f"{self.dbpath} is not a file.")
Subclass this to create custom ArgShells.
14 def do_use_db(self, arg: str): 15 """Set which database file to use.""" 16 dbpath = Pathier(arg) 17 if not dbpath.exists(): 18 print(f"{dbpath} does not exist.") 19 print(f"Still using {self.dbpath}") 20 elif not dbpath.is_file(): 21 print(f"{dbpath} is not a file.") 22 print(f"Still using {self.dbpath}") 23 else: 24 self.dbpath = dbpath
Set which database file to use.
30 @argshell.with_parser(dbparsers.get_backup_parser) 31 def do_backup(self, args: argshell.Namespace): 32 """Create a backup of the current db file.""" 33 print(f"Creating a back up for {self.dbpath}...") 34 backup_path = self.dbpath.backup(args.timestamp) 35 print("Creating backup is complete.") 36 print(f"Backup path: {backup_path}")
Create a backup of the current db file.
38 def do_size(self, arg: str): 39 """Display the size of the the current db file.""" 40 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.")
Display the size of the the current db file.
42 @argshell.with_parser(dbparsers.get_create_table_parser) 43 def do_add_table(self, args: argshell.Namespace): 44 """Add a new table to the database.""" 45 with DataBased(self.dbpath) as db: 46 db.create_table(args.table_name, args.columns)
Add a new table to the database.
48 def do_drop_table(self, arg: str): 49 """Drop the specified table.""" 50 with DataBased(self.dbpath) as db: 51 db.drop_table(arg)
Drop the specified table.
53 @argshell.with_parser( 54 dbparsers.get_add_row_parser, [dbparsers.verify_matching_length] 55 ) 56 def do_add_row(self, args: argshell.Namespace): 57 """Add a row to a table.""" 58 with DataBased(self.dbpath) as db: 59 if db.add_row(args.table_name, args.values, args.columns or None): 60 print(f"Added row to {args.table_name} table successfully.") 61 else: 62 print(f"Failed to add row to {args.table_name} table.")
Add a row to a table.
64 @argshell.with_parser(dbparsers.get_info_parser) 65 def do_info(self, args: argshell.Namespace): 66 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 67 print("Getting database info...") 68 with DataBased(self.dbpath) as db: 69 tables = args.tables or db.get_table_names() 70 info = [ 71 { 72 "Table Name": table, 73 "Columns": ", ".join(db.get_column_names(table)), 74 "Number of Rows": db.count(table) if args.rowcount else "n/a", 75 } 76 for table in tables 77 ] 78 print(DataBased.data_to_string(info))
Print out the names of the database tables, their columns, and, optionally, the number of rows.
80 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 81 def do_show(self, args: argshell.Namespace): 82 """Find and print rows from the database. 83 Use the -t/--tables, -m/--match_pairs, and -l/--limit flags to limit the search. 84 Use the -c/--columns flag to limit what columns are printed. 85 Use the -o/--order_by flag to order the results. 86 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs 87 Pass -h/--help flag for parser help.""" 88 print("Finding records... ") 89 if len(args.columns) == 0: 90 args.columns = None 91 with DataBased(self.dbpath) as db: 92 tables = args.tables or db.get_table_names() 93 for table in tables: 94 results = db.get_rows( 95 table, 96 args.match_pairs, 97 columns_to_return=args.columns, 98 order_by=args.order_by, 99 limit=args.limit, 100 exact_match=not args.partial_matching, 101 ) 102 db.close() 103 print(f"{len(results)} matching rows in {table} table.") 104 try: 105 print(DataBased.data_to_string(results)) # type: ignore 106 except Exception as e: 107 print("Couldn't fit data into a grid.") 108 print(*results, sep="\n") 109 if results: 110 print(f"{len(results)} matching rows in {table} table.") 111 print()
Find and print rows from the database. Use the -t/--tables, -m/--match_pairs, and -l/--limit flags to limit the search. Use the -c/--columns flag to limit what columns are printed. Use the -o/--order_by flag to order the results. Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs Pass -h/--help flag for parser help.
113 @argshell.with_parser(dbparsers.get_search_parser) 114 def do_search(self, args: argshell.Namespace): 115 """Search and return any rows containg the searched substring in any of its columns. 116 Use the -t/--tables flag to limit the search to a specific table(s). 117 Use the -c/--columns flag to limit the search to a specific column(s).""" 118 print(f"Searching for {args.search_string}...") 119 with DataBased(self.dbpath) as db: 120 tables = args.tables or db.get_table_names() 121 for table in tables: 122 columns = args.columns or db.get_column_names(table) 123 matcher = " OR ".join( 124 f'{column} LIKE "%{args.search_string}%"' for column in columns 125 ) 126 query = f"SELECT * FROM {table} WHERE {matcher};" 127 results = db.query(query) 128 results = [db._get_dict(table, result) for result in results] 129 print(f"Found {len(results)} results in {table} table.") 130 print(DataBased.data_to_string(results))
Search and return any rows containg the searched substring in any of its columns. Use the -t/--tables flag to limit the search to a specific table(s). Use the -c/--columns flag to limit the search to a specific column(s).
132 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 133 def do_count(self, args: argshell.Namespace): 134 """Print the number of rows in the database. 135 Use the -t/--tables flag to limit results to a specific table(s). 136 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 137 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 138 Pass -h/--help flag for parser help.""" 139 print("Counting rows...") 140 with DataBased(self.dbpath) as db: 141 tables = args.tables or db.get_table_names() 142 for table in tables: 143 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 144 print(f"{num_rows} matching rows in {table} table.")
Print the number of rows in the database. Use the -t/--tables flag to limit results to a specific table(s). Use the -m/--match_pairs flag to limit the results to rows matching these criteria. Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. Pass -h/--help flag for parser help.
146 def do_query(self, arg: str): 147 """Execute a query against the current database.""" 148 print(f"Executing {arg}") 149 with DataBased(self.dbpath) as db: 150 results = db.query(arg) 151 try: 152 try: 153 print(griddy(results)) 154 except Exception as e: 155 for result in results: 156 print(*result, sep="|-|") 157 except Exception as e: 158 print(f"{type(e).__name__}: {e}") 159 print(f"{db.cursor.rowcount} affected rows")
Execute a query against the current database.
161 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 162 def do_update(self, args: argshell.Namespace): 163 """Update a column to a new value. 164 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 165 Use the -t/--tables flag to limit what tables are updated. 166 Use the -m/--match_pairs flag to specify which rows are updated. 167 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 168 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 169 170 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 171 print("Updating rows...") 172 with DataBased(self.dbpath) as db: 173 tables = args.tables or db.get_table_names() 174 for table in tables: 175 num_updates = db.update( 176 table, 177 args.column, 178 args.new_value, 179 args.match_pairs, 180 not args.partial_matching, 181 ) 182 print(f"Updated {num_updates} rows in {table} table.")
Update a column to a new value. Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. Use the -t/--tables flag to limit what tables are updated. Use the -m/--match_pairs flag to specify which rows are updated. Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs.
>>> based>update -c username -v big_chungus -t users -m username lil_chungus
^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^
184 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 185 def do_delete(self, args: argshell.Namespace): 186 """Delete rows from the database. 187 Use the -t/--tables flag to limit what tables rows are deleted from. 188 Use the -m/--match_pairs flag to specify which rows are deleted. 189 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 190 >>> based>delete -t users -m username chungus -p 191 192 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 193 print("Deleting records...") 194 with DataBased(self.dbpath) as db: 195 tables = args.tables or db.get_table_names() 196 for table in tables: 197 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 198 print(f"Deleted {num_rows} rows from {table} table.")
Delete rows from the database. Use the -t/--tables flag to limit what tables rows are deleted from. Use the -m/--match_pairs flag to specify which rows are deleted. Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs.
>>> based>delete -t users -m username chungus -p
^will delete all rows in the 'users' table whose username contains 'chungus'^
200 @argshell.with_parser(dbparsers.get_add_column_parser) 201 def do_add_column(self, args: argshell.Namespace): 202 """Add a new column to the specified tables.""" 203 with DataBased(self.dbpath) as db: 204 tables = args.tables or db.get_table_names() 205 for table in tables: 206 db.add_column(table, args.column_name, args.type, args.default_value)
Add a new column to the specified tables.
208 def do_flush_log(self, arg: str): 209 """Clear the log file for this database.""" 210 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 211 if not log_path.exists(): 212 print(f"No log file at path {log_path}") 213 else: 214 print(f"Flushing log...") 215 log_path.write_text("")
Clear the log file for this database.
217 def do_scan_dbs(self, arg: str): 218 """Scan the current working directory for `*.db` files and display them. 219 220 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 221 cwd = Pathier.cwd() 222 if arg.strip() == "r": 223 dbs = cwd.rglob("*.db") 224 else: 225 dbs = cwd.glob("*.db") 226 for db in dbs: 227 print(db.separate(cwd.stem))
Scan the current working directory for *.db
files and display them.
If the command is entered as based>scan_dbs r
, the scan will be performed recursively.
229 def do_customize(self, arg: str): 230 """Generate a template file in the current working directory for creating a custom DBShell class. 231 Expects one argument: the name of the custom dbshell. 232 This will be used to name the generated file as well as several components in the file content.""" 233 try: 234 create_shell(arg) 235 except Exception as e: 236 print(f"{type(e).__name__}: {e}")
Generate a template file in the current working directory for creating a custom DBShell class. Expects one argument: the name of the custom dbshell. This will be used to name the generated file as well as several components in the file content.
238 def do_vacuum(self, arg: str): 239 """Reduce database disk memory.""" 240 starting_size = self.dbpath.size 241 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 242 print("Vacuuming database...") 243 with DataBased(self.dbpath) as db: 244 freedspace = db.vacuum() 245 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 246 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.")
Reduce database disk memory.
266 def preloop(self): 267 """Scan the current directory for a .db file to use. 268 If not found, prompt the user for one or to try again recursively.""" 269 if self.dbpath: 270 self.dbpath = Pathier(self.dbpath) 271 print(f"Defaulting to database {self.dbpath}") 272 else: 273 print("Searching for database...") 274 cwd = Pathier.cwd() 275 dbs = list(cwd.glob("*.db")) 276 if len(dbs) == 1: 277 self.dbpath = dbs[0] 278 print(f"Using database {self.dbpath}.") 279 elif dbs: 280 self.dbpath = self._choose_db(dbs) 281 else: 282 print(f"Could not find a .db file in {cwd}.") 283 path = input( 284 "Enter path to .db file to use or press enter to search again recursively: " 285 ) 286 if path: 287 self.dbpath = Pathier(path) 288 elif not path: 289 print("Searching recursively...") 290 dbs = list(cwd.rglob("*.db")) 291 if len(dbs) == 1: 292 self.dbpath = dbs[0] 293 print(f"Using database {self.dbpath}.") 294 elif dbs: 295 self.dbpath = self._choose_db(dbs) 296 else: 297 print("Could not find a .db file.") 298 self.dbpath = Pathier(input("Enter path to a .db file: ")) 299 if not self.dbpath.exists(): 300 raise FileNotFoundError(f"{self.dbpath} does not exist.") 301 if not self.dbpath.is_file(): 302 raise ValueError(f"{self.dbpath} is not a file.")
Scan the current directory for a .db file to use. If not found, prompt the user for one or to try again recursively.
Inherited Members
- cmd.Cmd
- Cmd
- precmd
- postcmd
- postloop
- parseline
- onecmd
- default
- completedefault
- completenames
- complete
- get_names
- complete_help
- print_topics
- columnize
- argshell.argshell.ArgShell
- do_quit
- do_sys
- do_help
- cmdloop
- emptyline