databased.dbshell
1import argshell 2from griddle import griddy 3from pathier import Pathier 4 5from databased import DataBased, 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.size(True)}.") 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 print() 109 110 @argshell.with_parser(dbparsers.get_search_parser) 111 def do_search(self, args: argshell.Namespace): 112 """Search and return any rows containg the searched substring in any of its columns. 113 Use the -t/--tables flag to limit the search to a specific table(s). 114 Use the -c/--columns flag to limit the search to a specific column(s).""" 115 print(f"Searching for {args.search_string}...") 116 with DataBased(self.dbpath) as db: 117 tables = args.tables or db.get_table_names() 118 for table in tables: 119 columns = args.columns or db.get_column_names(table) 120 matcher = " OR ".join( 121 f'{column} LIKE "%{args.search_string}%"' for column in columns 122 ) 123 query = f"SELECT * FROM {table} WHERE {matcher};" 124 results = db.query(query) 125 results = [db._get_dict(table, result) for result in results] 126 print(f"Found {len(results)} results in {table} table.") 127 print(DataBased.data_to_string(results)) 128 129 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 130 def do_count(self, args: argshell.Namespace): 131 """Print the number of rows in the database. 132 Use the -t/--tables flag to limit results to a specific table(s). 133 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 134 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 135 Pass -h/--help flag for parser help.""" 136 print("Counting rows...") 137 with DataBased(self.dbpath) as db: 138 tables = args.tables or db.get_table_names() 139 for table in tables: 140 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 141 print(f"{num_rows} matching rows in {table} table.") 142 143 def do_query(self, arg: str): 144 """Execute a query against the current database.""" 145 print(f"Executing {arg}") 146 with DataBased(self.dbpath) as db: 147 results = db.query(arg) 148 try: 149 try: 150 print(griddy(results)) 151 except Exception as e: 152 for result in results: 153 print(*result, sep="|-|") 154 except Exception as e: 155 print(f"{type(e).__name__}: {e}") 156 print(f"{db.cursor.rowcount} affected rows") 157 158 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 159 def do_update(self, args: argshell.Namespace): 160 """Update a column to a new value. 161 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 162 Use the -t/--tables flag to limit what tables are updated. 163 Use the -m/--match_pairs flag to specify which rows are updated. 164 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 165 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 166 167 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 168 print("Updating rows...") 169 with DataBased(self.dbpath) as db: 170 tables = args.tables or db.get_table_names() 171 for table in tables: 172 num_updates = db.update( 173 table, 174 args.column, 175 args.new_value, 176 args.match_pairs, 177 not args.partial_matching, 178 ) 179 print(f"Updated {num_updates} rows in {table} table.") 180 181 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 182 def do_delete(self, args: argshell.Namespace): 183 """Delete rows from the database. 184 Use the -t/--tables flag to limit what tables rows are deleted from. 185 Use the -m/--match_pairs flag to specify which rows are deleted. 186 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 187 >>> based>delete -t users -m username chungus -p 188 189 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 190 print("Deleting records...") 191 with DataBased(self.dbpath) as db: 192 tables = args.tables or db.get_table_names() 193 for table in tables: 194 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 195 print(f"Deleted {num_rows} rows from {table} table.") 196 197 @argshell.with_parser(dbparsers.get_add_column_parser) 198 def do_add_column(self, args: argshell.Namespace): 199 """Add a new column to the specified tables.""" 200 with DataBased(self.dbpath) as db: 201 tables = args.tables or db.get_table_names() 202 for table in tables: 203 db.add_column(table, args.column_name, args.type, args.default_value) 204 205 def do_flush_log(self, arg: str): 206 """Clear the log file for this database.""" 207 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 208 if not log_path.exists(): 209 print(f"No log file at path {log_path}") 210 else: 211 print(f"Flushing log...") 212 log_path.write_text("") 213 214 def do_scan_dbs(self, arg: str): 215 """Scan the current working directory for `*.db` files and display them. 216 217 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 218 cwd = Pathier.cwd() 219 if arg.strip() == "r": 220 dbs = cwd.rglob("*.db") 221 else: 222 dbs = cwd.glob("*.db") 223 for db in dbs: 224 print(db.separate(cwd.stem)) 225 226 def do_customize(self, arg: str): 227 """Generate a template file in the current working directory for creating a custom DBShell class. 228 Expects one argument: the name of the custom dbshell. 229 This will be used to name the generated file as well as several components in the file content.""" 230 custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py") 231 if custom_file.exists(): 232 print(f"Error: {custom_file.name} already exists in this location.") 233 else: 234 variable_name = "_".join(word for word in arg.lower().split()) 235 class_name = "".join(word.capitalize() for word in arg.split()) 236 content = (Pathier(__file__).parent / "customshell.py").read_text() 237 content = content.replace("CustomShell", class_name) 238 content = content.replace("customshell", variable_name) 239 custom_file.write_text(content) 240 241 def do_vacuum(self, arg: str): 242 """Reduce database disk memory.""" 243 starting_size = self.dbpath.size() 244 print(f"Database size before vacuuming: {self.dbpath.size(True)}") 245 print("Vacuuming database...") 246 with DataBased(self.dbpath) as db: 247 db.vacuum() 248 print(f"Database size after vacuuming: {self.dbpath.size(True)}") 249 print(f"Freed up {Pathier.format_size(starting_size - self.dbpath.size())} of disk space.") # type: ignore 250 251 def _choose_db(self, options: list[Pathier]) -> Pathier: 252 """Prompt the user to select from a list of files.""" 253 cwd = Pathier.cwd() 254 paths = [path.separate(cwd.stem) for path in options] 255 while True: 256 print( 257 f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}" 258 ) 259 choice = input("Enter the number of the option to use: ") 260 try: 261 index = int(choice) 262 if not 1 <= index <= len(options): 263 print("Choice out of range.") 264 continue 265 return options[index - 1] 266 except Exception as e: 267 print(f"{choice} is not a valid option.") 268 269 def preloop(self): 270 """Scan the current directory for a .db file to use. 271 If not found, prompt the user for one or to try again recursively.""" 272 if self.dbpath: 273 self.dbpath = Pathier(self.dbpath) 274 print(f"Defaulting to database {self.dbpath}") 275 else: 276 print("Searching for database...") 277 cwd = Pathier.cwd() 278 dbs = list(cwd.glob("*.db")) 279 if len(dbs) == 1: 280 self.dbpath = dbs[0] 281 print(f"Using database {self.dbpath}.") 282 elif dbs: 283 self.dbpath = self._choose_db(dbs) 284 else: 285 print(f"Could not find a .db file in {cwd}.") 286 path = input( 287 "Enter path to .db file to use or press enter to search again recursively: " 288 ) 289 if path: 290 self.dbpath = Pathier(path) 291 elif not path: 292 print("Searching recursively...") 293 dbs = list(cwd.rglob("*.db")) 294 if len(dbs) == 1: 295 self.dbpath = dbs[0] 296 print(f"Using database {self.dbpath}.") 297 elif dbs: 298 self.dbpath = self._choose_db(dbs) 299 else: 300 print("Could not find a .db file.") 301 self.dbpath = Pathier(input("Enter path to a .db file: ")) 302 if not self.dbpath.exists(): 303 raise FileNotFoundError(f"{self.dbpath} does not exist.") 304 if not self.dbpath.is_file(): 305 raise ValueError(f"{self.dbpath} is not a file.") 306 307 308def main(): 309 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.size(True)}.") 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 print() 110 111 @argshell.with_parser(dbparsers.get_search_parser) 112 def do_search(self, args: argshell.Namespace): 113 """Search and return any rows containg the searched substring in any of its columns. 114 Use the -t/--tables flag to limit the search to a specific table(s). 115 Use the -c/--columns flag to limit the search to a specific column(s).""" 116 print(f"Searching for {args.search_string}...") 117 with DataBased(self.dbpath) as db: 118 tables = args.tables or db.get_table_names() 119 for table in tables: 120 columns = args.columns or db.get_column_names(table) 121 matcher = " OR ".join( 122 f'{column} LIKE "%{args.search_string}%"' for column in columns 123 ) 124 query = f"SELECT * FROM {table} WHERE {matcher};" 125 results = db.query(query) 126 results = [db._get_dict(table, result) for result in results] 127 print(f"Found {len(results)} results in {table} table.") 128 print(DataBased.data_to_string(results)) 129 130 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 131 def do_count(self, args: argshell.Namespace): 132 """Print the number of rows in the database. 133 Use the -t/--tables flag to limit results to a specific table(s). 134 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 135 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 136 Pass -h/--help flag for parser help.""" 137 print("Counting rows...") 138 with DataBased(self.dbpath) as db: 139 tables = args.tables or db.get_table_names() 140 for table in tables: 141 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 142 print(f"{num_rows} matching rows in {table} table.") 143 144 def do_query(self, arg: str): 145 """Execute a query against the current database.""" 146 print(f"Executing {arg}") 147 with DataBased(self.dbpath) as db: 148 results = db.query(arg) 149 try: 150 try: 151 print(griddy(results)) 152 except Exception as e: 153 for result in results: 154 print(*result, sep="|-|") 155 except Exception as e: 156 print(f"{type(e).__name__}: {e}") 157 print(f"{db.cursor.rowcount} affected rows") 158 159 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 160 def do_update(self, args: argshell.Namespace): 161 """Update a column to a new value. 162 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 163 Use the -t/--tables flag to limit what tables are updated. 164 Use the -m/--match_pairs flag to specify which rows are updated. 165 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 166 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 167 168 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 169 print("Updating rows...") 170 with DataBased(self.dbpath) as db: 171 tables = args.tables or db.get_table_names() 172 for table in tables: 173 num_updates = db.update( 174 table, 175 args.column, 176 args.new_value, 177 args.match_pairs, 178 not args.partial_matching, 179 ) 180 print(f"Updated {num_updates} rows in {table} table.") 181 182 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 183 def do_delete(self, args: argshell.Namespace): 184 """Delete rows from the database. 185 Use the -t/--tables flag to limit what tables rows are deleted from. 186 Use the -m/--match_pairs flag to specify which rows are deleted. 187 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 188 >>> based>delete -t users -m username chungus -p 189 190 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 191 print("Deleting records...") 192 with DataBased(self.dbpath) as db: 193 tables = args.tables or db.get_table_names() 194 for table in tables: 195 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 196 print(f"Deleted {num_rows} rows from {table} table.") 197 198 @argshell.with_parser(dbparsers.get_add_column_parser) 199 def do_add_column(self, args: argshell.Namespace): 200 """Add a new column to the specified tables.""" 201 with DataBased(self.dbpath) as db: 202 tables = args.tables or db.get_table_names() 203 for table in tables: 204 db.add_column(table, args.column_name, args.type, args.default_value) 205 206 def do_flush_log(self, arg: str): 207 """Clear the log file for this database.""" 208 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 209 if not log_path.exists(): 210 print(f"No log file at path {log_path}") 211 else: 212 print(f"Flushing log...") 213 log_path.write_text("") 214 215 def do_scan_dbs(self, arg: str): 216 """Scan the current working directory for `*.db` files and display them. 217 218 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 219 cwd = Pathier.cwd() 220 if arg.strip() == "r": 221 dbs = cwd.rglob("*.db") 222 else: 223 dbs = cwd.glob("*.db") 224 for db in dbs: 225 print(db.separate(cwd.stem)) 226 227 def do_customize(self, arg: str): 228 """Generate a template file in the current working directory for creating a custom DBShell class. 229 Expects one argument: the name of the custom dbshell. 230 This will be used to name the generated file as well as several components in the file content.""" 231 custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py") 232 if custom_file.exists(): 233 print(f"Error: {custom_file.name} already exists in this location.") 234 else: 235 variable_name = "_".join(word for word in arg.lower().split()) 236 class_name = "".join(word.capitalize() for word in arg.split()) 237 content = (Pathier(__file__).parent / "customshell.py").read_text() 238 content = content.replace("CustomShell", class_name) 239 content = content.replace("customshell", variable_name) 240 custom_file.write_text(content) 241 242 def do_vacuum(self, arg: str): 243 """Reduce database disk memory.""" 244 starting_size = self.dbpath.size() 245 print(f"Database size before vacuuming: {self.dbpath.size(True)}") 246 print("Vacuuming database...") 247 with DataBased(self.dbpath) as db: 248 db.vacuum() 249 print(f"Database size after vacuuming: {self.dbpath.size(True)}") 250 print(f"Freed up {Pathier.format_size(starting_size - self.dbpath.size())} of disk space.") # type: ignore 251 252 def _choose_db(self, options: list[Pathier]) -> Pathier: 253 """Prompt the user to select from a list of files.""" 254 cwd = Pathier.cwd() 255 paths = [path.separate(cwd.stem) for path in options] 256 while True: 257 print( 258 f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}" 259 ) 260 choice = input("Enter the number of the option to use: ") 261 try: 262 index = int(choice) 263 if not 1 <= index <= len(options): 264 print("Choice out of range.") 265 continue 266 return options[index - 1] 267 except Exception as e: 268 print(f"{choice} is not a valid option.") 269 270 def preloop(self): 271 """Scan the current directory for a .db file to use. 272 If not found, prompt the user for one or to try again recursively.""" 273 if self.dbpath: 274 self.dbpath = Pathier(self.dbpath) 275 print(f"Defaulting to database {self.dbpath}") 276 else: 277 print("Searching for database...") 278 cwd = Pathier.cwd() 279 dbs = list(cwd.glob("*.db")) 280 if len(dbs) == 1: 281 self.dbpath = dbs[0] 282 print(f"Using database {self.dbpath}.") 283 elif dbs: 284 self.dbpath = self._choose_db(dbs) 285 else: 286 print(f"Could not find a .db file in {cwd}.") 287 path = input( 288 "Enter path to .db file to use or press enter to search again recursively: " 289 ) 290 if path: 291 self.dbpath = Pathier(path) 292 elif not path: 293 print("Searching recursively...") 294 dbs = list(cwd.rglob("*.db")) 295 if len(dbs) == 1: 296 self.dbpath = dbs[0] 297 print(f"Using database {self.dbpath}.") 298 elif dbs: 299 self.dbpath = self._choose_db(dbs) 300 else: 301 print("Could not find a .db file.") 302 self.dbpath = Pathier(input("Enter path to a .db file: ")) 303 if not self.dbpath.exists(): 304 raise FileNotFoundError(f"{self.dbpath} does not exist.") 305 if not self.dbpath.is_file(): 306 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.size(True)}.")
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 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.
111 @argshell.with_parser(dbparsers.get_search_parser) 112 def do_search(self, args: argshell.Namespace): 113 """Search and return any rows containg the searched substring in any of its columns. 114 Use the -t/--tables flag to limit the search to a specific table(s). 115 Use the -c/--columns flag to limit the search to a specific column(s).""" 116 print(f"Searching for {args.search_string}...") 117 with DataBased(self.dbpath) as db: 118 tables = args.tables or db.get_table_names() 119 for table in tables: 120 columns = args.columns or db.get_column_names(table) 121 matcher = " OR ".join( 122 f'{column} LIKE "%{args.search_string}%"' for column in columns 123 ) 124 query = f"SELECT * FROM {table} WHERE {matcher};" 125 results = db.query(query) 126 results = [db._get_dict(table, result) for result in results] 127 print(f"Found {len(results)} results in {table} table.") 128 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).
130 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 131 def do_count(self, args: argshell.Namespace): 132 """Print the number of rows in the database. 133 Use the -t/--tables flag to limit results to a specific table(s). 134 Use the -m/--match_pairs flag to limit the results to rows matching these criteria. 135 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 136 Pass -h/--help flag for parser help.""" 137 print("Counting rows...") 138 with DataBased(self.dbpath) as db: 139 tables = args.tables or db.get_table_names() 140 for table in tables: 141 num_rows = db.count(table, args.match_pairs, not args.partial_matching) 142 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.
144 def do_query(self, arg: str): 145 """Execute a query against the current database.""" 146 print(f"Executing {arg}") 147 with DataBased(self.dbpath) as db: 148 results = db.query(arg) 149 try: 150 try: 151 print(griddy(results)) 152 except Exception as e: 153 for result in results: 154 print(*result, sep="|-|") 155 except Exception as e: 156 print(f"{type(e).__name__}: {e}") 157 print(f"{db.cursor.rowcount} affected rows")
Execute a query against the current database.
159 @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs]) 160 def do_update(self, args: argshell.Namespace): 161 """Update a column to a new value. 162 Two required args: the column (-c/--column) to update and the value (-v/--value) to update to. 163 Use the -t/--tables flag to limit what tables are updated. 164 Use the -m/--match_pairs flag to specify which rows are updated. 165 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 166 >>> based>update -c username -v big_chungus -t users -m username lil_chungus 167 168 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^""" 169 print("Updating rows...") 170 with DataBased(self.dbpath) as db: 171 tables = args.tables or db.get_table_names() 172 for table in tables: 173 num_updates = db.update( 174 table, 175 args.column, 176 args.new_value, 177 args.match_pairs, 178 not args.partial_matching, 179 ) 180 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'^
182 @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs]) 183 def do_delete(self, args: argshell.Namespace): 184 """Delete rows from the database. 185 Use the -t/--tables flag to limit what tables rows are deleted from. 186 Use the -m/--match_pairs flag to specify which rows are deleted. 187 Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs. 188 >>> based>delete -t users -m username chungus -p 189 190 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 191 print("Deleting records...") 192 with DataBased(self.dbpath) as db: 193 tables = args.tables or db.get_table_names() 194 for table in tables: 195 num_rows = db.delete(table, args.match_pairs, not args.partial_matching) 196 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'^
198 @argshell.with_parser(dbparsers.get_add_column_parser) 199 def do_add_column(self, args: argshell.Namespace): 200 """Add a new column to the specified tables.""" 201 with DataBased(self.dbpath) as db: 202 tables = args.tables or db.get_table_names() 203 for table in tables: 204 db.add_column(table, args.column_name, args.type, args.default_value)
Add a new column to the specified tables.
206 def do_flush_log(self, arg: str): 207 """Clear the log file for this database.""" 208 log_path = self.dbpath.with_name(self.dbpath.stem + "db.log") 209 if not log_path.exists(): 210 print(f"No log file at path {log_path}") 211 else: 212 print(f"Flushing log...") 213 log_path.write_text("")
Clear the log file for this database.
215 def do_scan_dbs(self, arg: str): 216 """Scan the current working directory for `*.db` files and display them. 217 218 If the command is entered as `based>scan_dbs r`, the scan will be performed recursively.""" 219 cwd = Pathier.cwd() 220 if arg.strip() == "r": 221 dbs = cwd.rglob("*.db") 222 else: 223 dbs = cwd.glob("*.db") 224 for db in dbs: 225 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.
227 def do_customize(self, arg: str): 228 """Generate a template file in the current working directory for creating a custom DBShell class. 229 Expects one argument: the name of the custom dbshell. 230 This will be used to name the generated file as well as several components in the file content.""" 231 custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py") 232 if custom_file.exists(): 233 print(f"Error: {custom_file.name} already exists in this location.") 234 else: 235 variable_name = "_".join(word for word in arg.lower().split()) 236 class_name = "".join(word.capitalize() for word in arg.split()) 237 content = (Pathier(__file__).parent / "customshell.py").read_text() 238 content = content.replace("CustomShell", class_name) 239 content = content.replace("customshell", variable_name) 240 custom_file.write_text(content)
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.
242 def do_vacuum(self, arg: str): 243 """Reduce database disk memory.""" 244 starting_size = self.dbpath.size() 245 print(f"Database size before vacuuming: {self.dbpath.size(True)}") 246 print("Vacuuming database...") 247 with DataBased(self.dbpath) as db: 248 db.vacuum() 249 print(f"Database size after vacuuming: {self.dbpath.size(True)}") 250 print(f"Freed up {Pathier.format_size(starting_size - self.dbpath.size())} of disk space.") # type: ignore
Reduce database disk memory.
270 def preloop(self): 271 """Scan the current directory for a .db file to use. 272 If not found, prompt the user for one or to try again recursively.""" 273 if self.dbpath: 274 self.dbpath = Pathier(self.dbpath) 275 print(f"Defaulting to database {self.dbpath}") 276 else: 277 print("Searching for database...") 278 cwd = Pathier.cwd() 279 dbs = list(cwd.glob("*.db")) 280 if len(dbs) == 1: 281 self.dbpath = dbs[0] 282 print(f"Using database {self.dbpath}.") 283 elif dbs: 284 self.dbpath = self._choose_db(dbs) 285 else: 286 print(f"Could not find a .db file in {cwd}.") 287 path = input( 288 "Enter path to .db file to use or press enter to search again recursively: " 289 ) 290 if path: 291 self.dbpath = Pathier(path) 292 elif not path: 293 print("Searching recursively...") 294 dbs = list(cwd.rglob("*.db")) 295 if len(dbs) == 1: 296 self.dbpath = dbs[0] 297 print(f"Using database {self.dbpath}.") 298 elif dbs: 299 self.dbpath = self._choose_db(dbs) 300 else: 301 print("Could not find a .db file.") 302 self.dbpath = Pathier(input("Enter path to a .db file: ")) 303 if not self.dbpath.exists(): 304 raise FileNotFoundError(f"{self.dbpath} does not exist.") 305 if not self.dbpath.is_file(): 306 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