databased.dbshell
1import argshell 2from griddle import griddy 3from pathier import Pathier, Pathish 4 5from databased import Databased, __version__, dbparsers 6from databased.create_shell import create_shell 7 8 9class DBShell(argshell.ArgShell): 10 _dbpath: Pathier = None # type: ignore 11 connection_timeout: float = 10 12 detect_types: bool = True 13 enforce_foreign_keys: bool = True 14 intro = f"Starting dbshell v{__version__} (enter help or ? for arg info)...\n" 15 prompt = f"based>" 16 17 @property 18 def dbpath(self) -> Pathier: 19 return self._dbpath 20 21 @dbpath.setter 22 def dbpath(self, path: Pathish): 23 self._dbpath = Pathier(path) 24 self.prompt = f"{self._dbpath.name}>" 25 26 def _DB(self) -> Databased: 27 return Databased( 28 self.dbpath, 29 self.connection_timeout, 30 self.detect_types, 31 self.enforce_foreign_keys, 32 ) 33 34 def default(self, line: str): 35 line = line.strip("_") 36 with self._DB() as db: 37 self.display(db.query(line)) 38 39 def display(self, data: list[dict]): 40 """Print row data to terminal in a grid.""" 41 try: 42 print(griddy(data, "keys")) 43 except Exception as e: 44 print("Could not fit data into grid :(") 45 print(e) 46 47 # Seat 48 49 @argshell.with_parser(dbparsers.get_add_column_parser) 50 def do_add_column(self, args: argshell.Namespace): 51 """Add a new column to the specified tables.""" 52 with self._DB() as db: 53 db.add_column(args.table, args.column_def) 54 55 @argshell.with_parser(dbparsers.get_backup_parser) 56 def do_backup(self, args: argshell.Namespace): 57 """Create a backup of the current db file.""" 58 print(f"Creating a back up for {self.dbpath}...") 59 backup_path = self.dbpath.backup(args.timestamp) 60 print("Creating backup is complete.") 61 print(f"Backup path: {backup_path}") 62 63 def do_customize(self, name: str): 64 """Generate a template file in the current working directory for creating a custom DBShell class. 65 Expects one argument: the name of the custom dbshell. 66 This will be used to name the generated file as well as several components in the file content. 67 """ 68 try: 69 create_shell(name) 70 except Exception as e: 71 print(f"{type(e).__name__}: {e}") 72 73 def do_dbpath(self, _: str): 74 """Print the .db file in use.""" 75 print(self.dbpath) 76 77 @argshell.with_parser(dbparsers.get_delete_parser) 78 def do_delete(self, args: argshell.Namespace): 79 """Delete rows from the database. 80 81 Syntax: 82 >>> delete {table} {where} 83 >>> based>delete users "username LIKE '%chungus%" 84 85 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 86 print("Deleting records...") 87 with self._DB() as db: 88 num_rows = db.delete(args.table, args.where) 89 print(f"Deleted {num_rows} rows from {args.table} table.") 90 91 def do_describe(self, tables: str): 92 """Describe each table in `tables`. If no table list is given, all tables will be described.""" 93 with self._DB() as db: 94 table_list = tables.split() or db.tables 95 for table in table_list: 96 print(f"<{table}>") 97 print(db.to_grid(db.describe(table))) 98 99 @argshell.with_parser(dbparsers.get_drop_column_parser) 100 def do_drop_column(self, args: argshell.Namespace): 101 """Drop the specified column from the specified table.""" 102 with self._DB() as db: 103 db.drop_column(args.table, args.column) 104 105 def do_drop_table(self, table: str): 106 """Drop the specified table.""" 107 with self._DB() as db: 108 db.drop_table(table) 109 110 def do_flush_log(self, _: str): 111 """Clear the log file for this database.""" 112 log_path = self.dbpath.with_name(self.dbpath.name.replace(".", "") + ".log") 113 if not log_path.exists(): 114 print(f"No log file at path {log_path}") 115 else: 116 print(f"Flushing log...") 117 log_path.write_text("") 118 119 def do_help(self, args: str): 120 """Display help messages.""" 121 super().do_help(args) 122 if args == "": 123 print("Unrecognized commands will be executed as queries.") 124 print( 125 "Use the `query` command explicitly if you don't want to capitalize your key words." 126 ) 127 print("All transactions initiated by commands are committed immediately.") 128 print() 129 130 def do_properties(self, _: str): 131 """See current database property settings.""" 132 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 133 print(f"{property_}: {getattr(self, property_)}") 134 135 def do_query(self, query: str): 136 """Execute a query against the current database.""" 137 print(f"Executing {query}") 138 with self._DB() as db: 139 results = db.query(query) 140 self.display(results) 141 print(f"{db.cursor.rowcount} affected rows") 142 143 @argshell.with_parser(dbparsers.get_rename_column_parser) 144 def do_rename_column(self, args: argshell.Namespace): 145 """Rename a column.""" 146 with self._DB() as db: 147 db.rename_column(args.table, args.column, args.new_name) 148 149 @argshell.with_parser(dbparsers.get_rename_table_parser) 150 def do_rename_table(self, args: argshell.Namespace): 151 """Rename a table.""" 152 with self._DB() as db: 153 db.rename_table(args.table, args.new_name) 154 155 def do_restore(self, file: str): 156 """Replace the current db file with the given db backup file.""" 157 backup = Pathier(file.strip('"')) 158 if not backup.exists(): 159 print(f"{backup} does not exist.") 160 else: 161 print(f"Restoring from {file}...") 162 self.dbpath.write_bytes(backup.read_bytes()) 163 print("Restore complete.") 164 165 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 166 def do_scan(self, args: argshell.Namespace): 167 """Scan the current working directory for database files.""" 168 dbs = self._scan(args.extensions, args.recursive) 169 for db in dbs: 170 print(db.separate(Pathier.cwd().stem)) 171 172 @argshell.with_parser(dbparsers.get_schema_parser) 173 def do_schema(self, args: argshell.Namespace): 174 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 175 print("Getting database schema...") 176 with self._DB() as db: 177 tables = args.tables or db.tables 178 info = [ 179 { 180 "Table Name": table, 181 "Columns": ", ".join(db.get_columns(table)), 182 "Number of Rows": db.count(table) if args.rowcount else "n/a", 183 } 184 for table in tables 185 ] 186 self.display(info) 187 188 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 189 def do_select(self, args: argshell.Namespace): 190 """Execute a SELECT query with the given args.""" 191 print(f"Querying {args.table}... ") 192 with self._DB() as db: 193 rows = db.select( 194 table=args.table, 195 columns=args.columns, 196 joins=args.joins, 197 where=args.where, 198 group_by=args.group_by, 199 having=args.Having, 200 order_by=args.order_by, 201 limit=args.limit, 202 ) 203 print(f"Found {len(rows)} rows:") 204 self.display(rows) 205 print(f"{len(rows)} rows from {args.table}") 206 207 def do_set_connection_timeout(self, seconds: str): 208 """Set database connection timeout to this number of seconds.""" 209 self.connection_timeout = float(seconds) 210 211 def do_set_detect_types(self, should_detect: str): 212 """Pass a `1` to turn on and a `0` to turn off.""" 213 self.detect_types = bool(int(should_detect)) 214 215 def do_set_enforce_foreign_keys(self, should_enforce: str): 216 """Pass a `1` to turn on and a `0` to turn off.""" 217 self.enforce_foreign_keys = bool(int(should_enforce)) 218 219 def do_size(self, _: str): 220 """Display the size of the the current db file.""" 221 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 222 223 @argshell.with_parser(dbparsers.get_update_parser) 224 def do_update(self, args: argshell.Namespace): 225 """Update a column to a new value. 226 227 Syntax: 228 >>> update {table} {column} {value} {where} 229 >>> based>update users username big_chungus "username = lil_chungus" 230 231 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 232 """ 233 print("Updating rows...") 234 with self._DB() as db: 235 num_updates = db.update(args.table, args.column, args.new_value, args.where) 236 print(f"Updated {num_updates} rows in table {args.table}.") 237 238 def do_use(self, arg: str): 239 """Set which database file to use.""" 240 dbpath = Pathier(arg) 241 if not dbpath.exists(): 242 print(f"{dbpath} does not exist.") 243 print(f"Still using {self.dbpath}") 244 elif not dbpath.is_file(): 245 print(f"{dbpath} is not a file.") 246 print(f"Still using {self.dbpath}") 247 else: 248 self.dbpath = dbpath 249 self.prompt = f"{self.dbpath.name}>" 250 251 def do_vacuum(self, _: str): 252 """Reduce database disk memory.""" 253 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 254 print("Vacuuming database...") 255 with self._DB() as db: 256 freedspace = db.vacuum() 257 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 258 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.") 259 260 # Seat 261 262 def _choose_db(self, options: list[Pathier]) -> Pathier: 263 """Prompt the user to select from a list of files.""" 264 cwd = Pathier.cwd() 265 paths = [path.separate(cwd.stem) for path in options] 266 while True: 267 print( 268 f"DB options:\n{' '.join([f'({i}) {path}' for i, path in enumerate(paths, 1)])}" 269 ) 270 choice = input("Enter the number of the option to use: ") 271 try: 272 index = int(choice) 273 if not 1 <= index <= len(options): 274 print("Choice out of range.") 275 continue 276 return options[index - 1] 277 except Exception as e: 278 print(f"{choice} is not a valid option.") 279 280 def _scan( 281 self, extensions: list[str] = [".sqlite3", ".db"], recursive: bool = False 282 ) -> list[Pathier]: 283 cwd = Pathier.cwd() 284 dbs = [] 285 globber = cwd.glob 286 if recursive: 287 globber = cwd.rglob 288 for extension in extensions: 289 dbs.extend(list(globber(f"*{extension}"))) 290 return dbs 291 292 def preloop(self): 293 """Scan the current directory for a .db file to use. 294 If not found, prompt the user for one or to try again recursively.""" 295 if self.dbpath: 296 self.dbpath = Pathier(self.dbpath) 297 print(f"Defaulting to database {self.dbpath}") 298 else: 299 print("Searching for database...") 300 cwd = Pathier.cwd() 301 dbs = self._scan() 302 if len(dbs) == 1: 303 self.dbpath = dbs[0] 304 print(f"Using database {self.dbpath}.") 305 elif dbs: 306 self.dbpath = self._choose_db(dbs) 307 else: 308 print(f"Could not find a .db file in {cwd}.") 309 path = input( 310 "Enter path to .db file to use or press enter to search again recursively: " 311 ) 312 if path: 313 self.dbpath = Pathier(path) 314 elif not path: 315 print("Searching recursively...") 316 dbs = self._scan(recursive=True) 317 if len(dbs) == 1: 318 self.dbpath = dbs[0] 319 print(f"Using database {self.dbpath}.") 320 elif dbs: 321 self.dbpath = self._choose_db(dbs) 322 else: 323 print("Could not find a .db file.") 324 self.dbpath = Pathier(input("Enter path to a .db file: ")) 325 if not self.dbpath.exists(): 326 raise FileNotFoundError(f"{self.dbpath} does not exist.") 327 if not self.dbpath.is_file(): 328 raise ValueError(f"{self.dbpath} is not a file.") 329 330 331def main(): 332 DBShell().cmdloop()
10class DBShell(argshell.ArgShell): 11 _dbpath: Pathier = None # type: ignore 12 connection_timeout: float = 10 13 detect_types: bool = True 14 enforce_foreign_keys: bool = True 15 intro = f"Starting dbshell v{__version__} (enter help or ? for arg info)...\n" 16 prompt = f"based>" 17 18 @property 19 def dbpath(self) -> Pathier: 20 return self._dbpath 21 22 @dbpath.setter 23 def dbpath(self, path: Pathish): 24 self._dbpath = Pathier(path) 25 self.prompt = f"{self._dbpath.name}>" 26 27 def _DB(self) -> Databased: 28 return Databased( 29 self.dbpath, 30 self.connection_timeout, 31 self.detect_types, 32 self.enforce_foreign_keys, 33 ) 34 35 def default(self, line: str): 36 line = line.strip("_") 37 with self._DB() as db: 38 self.display(db.query(line)) 39 40 def display(self, data: list[dict]): 41 """Print row data to terminal in a grid.""" 42 try: 43 print(griddy(data, "keys")) 44 except Exception as e: 45 print("Could not fit data into grid :(") 46 print(e) 47 48 # Seat 49 50 @argshell.with_parser(dbparsers.get_add_column_parser) 51 def do_add_column(self, args: argshell.Namespace): 52 """Add a new column to the specified tables.""" 53 with self._DB() as db: 54 db.add_column(args.table, args.column_def) 55 56 @argshell.with_parser(dbparsers.get_backup_parser) 57 def do_backup(self, args: argshell.Namespace): 58 """Create a backup of the current db file.""" 59 print(f"Creating a back up for {self.dbpath}...") 60 backup_path = self.dbpath.backup(args.timestamp) 61 print("Creating backup is complete.") 62 print(f"Backup path: {backup_path}") 63 64 def do_customize(self, name: str): 65 """Generate a template file in the current working directory for creating a custom DBShell class. 66 Expects one argument: the name of the custom dbshell. 67 This will be used to name the generated file as well as several components in the file content. 68 """ 69 try: 70 create_shell(name) 71 except Exception as e: 72 print(f"{type(e).__name__}: {e}") 73 74 def do_dbpath(self, _: str): 75 """Print the .db file in use.""" 76 print(self.dbpath) 77 78 @argshell.with_parser(dbparsers.get_delete_parser) 79 def do_delete(self, args: argshell.Namespace): 80 """Delete rows from the database. 81 82 Syntax: 83 >>> delete {table} {where} 84 >>> based>delete users "username LIKE '%chungus%" 85 86 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 87 print("Deleting records...") 88 with self._DB() as db: 89 num_rows = db.delete(args.table, args.where) 90 print(f"Deleted {num_rows} rows from {args.table} table.") 91 92 def do_describe(self, tables: str): 93 """Describe each table in `tables`. If no table list is given, all tables will be described.""" 94 with self._DB() as db: 95 table_list = tables.split() or db.tables 96 for table in table_list: 97 print(f"<{table}>") 98 print(db.to_grid(db.describe(table))) 99 100 @argshell.with_parser(dbparsers.get_drop_column_parser) 101 def do_drop_column(self, args: argshell.Namespace): 102 """Drop the specified column from the specified table.""" 103 with self._DB() as db: 104 db.drop_column(args.table, args.column) 105 106 def do_drop_table(self, table: str): 107 """Drop the specified table.""" 108 with self._DB() as db: 109 db.drop_table(table) 110 111 def do_flush_log(self, _: str): 112 """Clear the log file for this database.""" 113 log_path = self.dbpath.with_name(self.dbpath.name.replace(".", "") + ".log") 114 if not log_path.exists(): 115 print(f"No log file at path {log_path}") 116 else: 117 print(f"Flushing log...") 118 log_path.write_text("") 119 120 def do_help(self, args: str): 121 """Display help messages.""" 122 super().do_help(args) 123 if args == "": 124 print("Unrecognized commands will be executed as queries.") 125 print( 126 "Use the `query` command explicitly if you don't want to capitalize your key words." 127 ) 128 print("All transactions initiated by commands are committed immediately.") 129 print() 130 131 def do_properties(self, _: str): 132 """See current database property settings.""" 133 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 134 print(f"{property_}: {getattr(self, property_)}") 135 136 def do_query(self, query: str): 137 """Execute a query against the current database.""" 138 print(f"Executing {query}") 139 with self._DB() as db: 140 results = db.query(query) 141 self.display(results) 142 print(f"{db.cursor.rowcount} affected rows") 143 144 @argshell.with_parser(dbparsers.get_rename_column_parser) 145 def do_rename_column(self, args: argshell.Namespace): 146 """Rename a column.""" 147 with self._DB() as db: 148 db.rename_column(args.table, args.column, args.new_name) 149 150 @argshell.with_parser(dbparsers.get_rename_table_parser) 151 def do_rename_table(self, args: argshell.Namespace): 152 """Rename a table.""" 153 with self._DB() as db: 154 db.rename_table(args.table, args.new_name) 155 156 def do_restore(self, file: str): 157 """Replace the current db file with the given db backup file.""" 158 backup = Pathier(file.strip('"')) 159 if not backup.exists(): 160 print(f"{backup} does not exist.") 161 else: 162 print(f"Restoring from {file}...") 163 self.dbpath.write_bytes(backup.read_bytes()) 164 print("Restore complete.") 165 166 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 167 def do_scan(self, args: argshell.Namespace): 168 """Scan the current working directory for database files.""" 169 dbs = self._scan(args.extensions, args.recursive) 170 for db in dbs: 171 print(db.separate(Pathier.cwd().stem)) 172 173 @argshell.with_parser(dbparsers.get_schema_parser) 174 def do_schema(self, args: argshell.Namespace): 175 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 176 print("Getting database schema...") 177 with self._DB() as db: 178 tables = args.tables or db.tables 179 info = [ 180 { 181 "Table Name": table, 182 "Columns": ", ".join(db.get_columns(table)), 183 "Number of Rows": db.count(table) if args.rowcount else "n/a", 184 } 185 for table in tables 186 ] 187 self.display(info) 188 189 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 190 def do_select(self, args: argshell.Namespace): 191 """Execute a SELECT query with the given args.""" 192 print(f"Querying {args.table}... ") 193 with self._DB() as db: 194 rows = db.select( 195 table=args.table, 196 columns=args.columns, 197 joins=args.joins, 198 where=args.where, 199 group_by=args.group_by, 200 having=args.Having, 201 order_by=args.order_by, 202 limit=args.limit, 203 ) 204 print(f"Found {len(rows)} rows:") 205 self.display(rows) 206 print(f"{len(rows)} rows from {args.table}") 207 208 def do_set_connection_timeout(self, seconds: str): 209 """Set database connection timeout to this number of seconds.""" 210 self.connection_timeout = float(seconds) 211 212 def do_set_detect_types(self, should_detect: str): 213 """Pass a `1` to turn on and a `0` to turn off.""" 214 self.detect_types = bool(int(should_detect)) 215 216 def do_set_enforce_foreign_keys(self, should_enforce: str): 217 """Pass a `1` to turn on and a `0` to turn off.""" 218 self.enforce_foreign_keys = bool(int(should_enforce)) 219 220 def do_size(self, _: str): 221 """Display the size of the the current db file.""" 222 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 223 224 @argshell.with_parser(dbparsers.get_update_parser) 225 def do_update(self, args: argshell.Namespace): 226 """Update a column to a new value. 227 228 Syntax: 229 >>> update {table} {column} {value} {where} 230 >>> based>update users username big_chungus "username = lil_chungus" 231 232 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 233 """ 234 print("Updating rows...") 235 with self._DB() as db: 236 num_updates = db.update(args.table, args.column, args.new_value, args.where) 237 print(f"Updated {num_updates} rows in table {args.table}.") 238 239 def do_use(self, arg: str): 240 """Set which database file to use.""" 241 dbpath = Pathier(arg) 242 if not dbpath.exists(): 243 print(f"{dbpath} does not exist.") 244 print(f"Still using {self.dbpath}") 245 elif not dbpath.is_file(): 246 print(f"{dbpath} is not a file.") 247 print(f"Still using {self.dbpath}") 248 else: 249 self.dbpath = dbpath 250 self.prompt = f"{self.dbpath.name}>" 251 252 def do_vacuum(self, _: str): 253 """Reduce database disk memory.""" 254 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 255 print("Vacuuming database...") 256 with self._DB() as db: 257 freedspace = db.vacuum() 258 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 259 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.") 260 261 # Seat 262 263 def _choose_db(self, options: list[Pathier]) -> Pathier: 264 """Prompt the user to select from a list of files.""" 265 cwd = Pathier.cwd() 266 paths = [path.separate(cwd.stem) for path in options] 267 while True: 268 print( 269 f"DB options:\n{' '.join([f'({i}) {path}' for i, path in enumerate(paths, 1)])}" 270 ) 271 choice = input("Enter the number of the option to use: ") 272 try: 273 index = int(choice) 274 if not 1 <= index <= len(options): 275 print("Choice out of range.") 276 continue 277 return options[index - 1] 278 except Exception as e: 279 print(f"{choice} is not a valid option.") 280 281 def _scan( 282 self, extensions: list[str] = [".sqlite3", ".db"], recursive: bool = False 283 ) -> list[Pathier]: 284 cwd = Pathier.cwd() 285 dbs = [] 286 globber = cwd.glob 287 if recursive: 288 globber = cwd.rglob 289 for extension in extensions: 290 dbs.extend(list(globber(f"*{extension}"))) 291 return dbs 292 293 def preloop(self): 294 """Scan the current directory for a .db file to use. 295 If not found, prompt the user for one or to try again recursively.""" 296 if self.dbpath: 297 self.dbpath = Pathier(self.dbpath) 298 print(f"Defaulting to database {self.dbpath}") 299 else: 300 print("Searching for database...") 301 cwd = Pathier.cwd() 302 dbs = self._scan() 303 if len(dbs) == 1: 304 self.dbpath = dbs[0] 305 print(f"Using database {self.dbpath}.") 306 elif dbs: 307 self.dbpath = self._choose_db(dbs) 308 else: 309 print(f"Could not find a .db file in {cwd}.") 310 path = input( 311 "Enter path to .db file to use or press enter to search again recursively: " 312 ) 313 if path: 314 self.dbpath = Pathier(path) 315 elif not path: 316 print("Searching recursively...") 317 dbs = self._scan(recursive=True) 318 if len(dbs) == 1: 319 self.dbpath = dbs[0] 320 print(f"Using database {self.dbpath}.") 321 elif dbs: 322 self.dbpath = self._choose_db(dbs) 323 else: 324 print("Could not find a .db file.") 325 self.dbpath = Pathier(input("Enter path to a .db file: ")) 326 if not self.dbpath.exists(): 327 raise FileNotFoundError(f"{self.dbpath} does not exist.") 328 if not self.dbpath.is_file(): 329 raise ValueError(f"{self.dbpath} is not a file.")
Subclass this to create custom ArgShells.
35 def default(self, line: str): 36 line = line.strip("_") 37 with self._DB() as db: 38 self.display(db.query(line))
Called on an input line when the command prefix is not recognized.
If this method is not overridden, it prints an error message and returns.
40 def display(self, data: list[dict]): 41 """Print row data to terminal in a grid.""" 42 try: 43 print(griddy(data, "keys")) 44 except Exception as e: 45 print("Could not fit data into grid :(") 46 print(e)
Print row data to terminal in a grid.
50 @argshell.with_parser(dbparsers.get_add_column_parser) 51 def do_add_column(self, args: argshell.Namespace): 52 """Add a new column to the specified tables.""" 53 with self._DB() as db: 54 db.add_column(args.table, args.column_def)
Add a new column to the specified tables.
56 @argshell.with_parser(dbparsers.get_backup_parser) 57 def do_backup(self, args: argshell.Namespace): 58 """Create a backup of the current db file.""" 59 print(f"Creating a back up for {self.dbpath}...") 60 backup_path = self.dbpath.backup(args.timestamp) 61 print("Creating backup is complete.") 62 print(f"Backup path: {backup_path}")
Create a backup of the current db file.
64 def do_customize(self, name: str): 65 """Generate a template file in the current working directory for creating a custom DBShell class. 66 Expects one argument: the name of the custom dbshell. 67 This will be used to name the generated file as well as several components in the file content. 68 """ 69 try: 70 create_shell(name) 71 except Exception as e: 72 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.
78 @argshell.with_parser(dbparsers.get_delete_parser) 79 def do_delete(self, args: argshell.Namespace): 80 """Delete rows from the database. 81 82 Syntax: 83 >>> delete {table} {where} 84 >>> based>delete users "username LIKE '%chungus%" 85 86 ^will delete all rows in the 'users' table whose username contains 'chungus'^""" 87 print("Deleting records...") 88 with self._DB() as db: 89 num_rows = db.delete(args.table, args.where) 90 print(f"Deleted {num_rows} rows from {args.table} table.")
Delete rows from the database.
Syntax:
>>> delete {table} {where}
>>> based>delete users "username LIKE '%chungus%"
^will delete all rows in the 'users' table whose username contains 'chungus'^
92 def do_describe(self, tables: str): 93 """Describe each table in `tables`. If no table list is given, all tables will be described.""" 94 with self._DB() as db: 95 table_list = tables.split() or db.tables 96 for table in table_list: 97 print(f"<{table}>") 98 print(db.to_grid(db.describe(table)))
Describe each table in tables
. If no table list is given, all tables will be described.
100 @argshell.with_parser(dbparsers.get_drop_column_parser) 101 def do_drop_column(self, args: argshell.Namespace): 102 """Drop the specified column from the specified table.""" 103 with self._DB() as db: 104 db.drop_column(args.table, args.column)
Drop the specified column from the specified table.
106 def do_drop_table(self, table: str): 107 """Drop the specified table.""" 108 with self._DB() as db: 109 db.drop_table(table)
Drop the specified table.
111 def do_flush_log(self, _: str): 112 """Clear the log file for this database.""" 113 log_path = self.dbpath.with_name(self.dbpath.name.replace(".", "") + ".log") 114 if not log_path.exists(): 115 print(f"No log file at path {log_path}") 116 else: 117 print(f"Flushing log...") 118 log_path.write_text("")
Clear the log file for this database.
120 def do_help(self, args: str): 121 """Display help messages.""" 122 super().do_help(args) 123 if args == "": 124 print("Unrecognized commands will be executed as queries.") 125 print( 126 "Use the `query` command explicitly if you don't want to capitalize your key words." 127 ) 128 print("All transactions initiated by commands are committed immediately.") 129 print()
Display help messages.
131 def do_properties(self, _: str): 132 """See current database property settings.""" 133 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 134 print(f"{property_}: {getattr(self, property_)}")
See current database property settings.
136 def do_query(self, query: str): 137 """Execute a query against the current database.""" 138 print(f"Executing {query}") 139 with self._DB() as db: 140 results = db.query(query) 141 self.display(results) 142 print(f"{db.cursor.rowcount} affected rows")
Execute a query against the current database.
144 @argshell.with_parser(dbparsers.get_rename_column_parser) 145 def do_rename_column(self, args: argshell.Namespace): 146 """Rename a column.""" 147 with self._DB() as db: 148 db.rename_column(args.table, args.column, args.new_name)
Rename a column.
150 @argshell.with_parser(dbparsers.get_rename_table_parser) 151 def do_rename_table(self, args: argshell.Namespace): 152 """Rename a table.""" 153 with self._DB() as db: 154 db.rename_table(args.table, args.new_name)
Rename a table.
156 def do_restore(self, file: str): 157 """Replace the current db file with the given db backup file.""" 158 backup = Pathier(file.strip('"')) 159 if not backup.exists(): 160 print(f"{backup} does not exist.") 161 else: 162 print(f"Restoring from {file}...") 163 self.dbpath.write_bytes(backup.read_bytes()) 164 print("Restore complete.")
Replace the current db file with the given db backup file.
166 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 167 def do_scan(self, args: argshell.Namespace): 168 """Scan the current working directory for database files.""" 169 dbs = self._scan(args.extensions, args.recursive) 170 for db in dbs: 171 print(db.separate(Pathier.cwd().stem))
Scan the current working directory for database files.
173 @argshell.with_parser(dbparsers.get_schema_parser) 174 def do_schema(self, args: argshell.Namespace): 175 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 176 print("Getting database schema...") 177 with self._DB() as db: 178 tables = args.tables or db.tables 179 info = [ 180 { 181 "Table Name": table, 182 "Columns": ", ".join(db.get_columns(table)), 183 "Number of Rows": db.count(table) if args.rowcount else "n/a", 184 } 185 for table in tables 186 ] 187 self.display(info)
Print out the names of the database tables, their columns, and, optionally, the number of rows.
189 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 190 def do_select(self, args: argshell.Namespace): 191 """Execute a SELECT query with the given args.""" 192 print(f"Querying {args.table}... ") 193 with self._DB() as db: 194 rows = db.select( 195 table=args.table, 196 columns=args.columns, 197 joins=args.joins, 198 where=args.where, 199 group_by=args.group_by, 200 having=args.Having, 201 order_by=args.order_by, 202 limit=args.limit, 203 ) 204 print(f"Found {len(rows)} rows:") 205 self.display(rows) 206 print(f"{len(rows)} rows from {args.table}")
Execute a SELECT query with the given args.
208 def do_set_connection_timeout(self, seconds: str): 209 """Set database connection timeout to this number of seconds.""" 210 self.connection_timeout = float(seconds)
Set database connection timeout to this number of seconds.
212 def do_set_detect_types(self, should_detect: str): 213 """Pass a `1` to turn on and a `0` to turn off.""" 214 self.detect_types = bool(int(should_detect))
Pass a 1
to turn on and a 0
to turn off.
216 def do_set_enforce_foreign_keys(self, should_enforce: str): 217 """Pass a `1` to turn on and a `0` to turn off.""" 218 self.enforce_foreign_keys = bool(int(should_enforce))
Pass a 1
to turn on and a 0
to turn off.
220 def do_size(self, _: str): 221 """Display the size of the the current db file.""" 222 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.")
Display the size of the the current db file.
224 @argshell.with_parser(dbparsers.get_update_parser) 225 def do_update(self, args: argshell.Namespace): 226 """Update a column to a new value. 227 228 Syntax: 229 >>> update {table} {column} {value} {where} 230 >>> based>update users username big_chungus "username = lil_chungus" 231 232 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 233 """ 234 print("Updating rows...") 235 with self._DB() as db: 236 num_updates = db.update(args.table, args.column, args.new_value, args.where) 237 print(f"Updated {num_updates} rows in table {args.table}.")
Update a column to a new value.
Syntax:
>>> update {table} {column} {value} {where}
>>> based>update users username big_chungus "username = lil_chungus"
^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^
239 def do_use(self, arg: str): 240 """Set which database file to use.""" 241 dbpath = Pathier(arg) 242 if not dbpath.exists(): 243 print(f"{dbpath} does not exist.") 244 print(f"Still using {self.dbpath}") 245 elif not dbpath.is_file(): 246 print(f"{dbpath} is not a file.") 247 print(f"Still using {self.dbpath}") 248 else: 249 self.dbpath = dbpath 250 self.prompt = f"{self.dbpath.name}>"
Set which database file to use.
252 def do_vacuum(self, _: str): 253 """Reduce database disk memory.""" 254 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 255 print("Vacuuming database...") 256 with self._DB() as db: 257 freedspace = db.vacuum() 258 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 259 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.")
Reduce database disk memory.
293 def preloop(self): 294 """Scan the current directory for a .db file to use. 295 If not found, prompt the user for one or to try again recursively.""" 296 if self.dbpath: 297 self.dbpath = Pathier(self.dbpath) 298 print(f"Defaulting to database {self.dbpath}") 299 else: 300 print("Searching for database...") 301 cwd = Pathier.cwd() 302 dbs = self._scan() 303 if len(dbs) == 1: 304 self.dbpath = dbs[0] 305 print(f"Using database {self.dbpath}.") 306 elif dbs: 307 self.dbpath = self._choose_db(dbs) 308 else: 309 print(f"Could not find a .db file in {cwd}.") 310 path = input( 311 "Enter path to .db file to use or press enter to search again recursively: " 312 ) 313 if path: 314 self.dbpath = Pathier(path) 315 elif not path: 316 print("Searching recursively...") 317 dbs = self._scan(recursive=True) 318 if len(dbs) == 1: 319 self.dbpath = dbs[0] 320 print(f"Using database {self.dbpath}.") 321 elif dbs: 322 self.dbpath = self._choose_db(dbs) 323 else: 324 print("Could not find a .db file.") 325 self.dbpath = Pathier(input("Enter path to a .db file: ")) 326 if not self.dbpath.exists(): 327 raise FileNotFoundError(f"{self.dbpath} does not exist.") 328 if not self.dbpath.is_file(): 329 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
- completedefault
- completenames
- complete
- get_names
- complete_help
- print_topics
- columnize
- argshell.argshell.ArgShell
- do_quit
- do_sys
- cmdloop
- emptyline