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 @argshell.with_parser(dbparsers.get_schema_parser) 131 def do_schema(self, args: argshell.Namespace): 132 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 133 print("Getting database schema...") 134 with self._DB() as db: 135 tables = args.tables or db.tables 136 info = [ 137 { 138 "Table Name": table, 139 "Columns": ", ".join(db.get_columns(table)), 140 "Number of Rows": db.count(table) if args.rowcount else "n/a", 141 } 142 for table in tables 143 ] 144 self.display(info) 145 146 def do_properties(self, _: str): 147 """See current database property settings.""" 148 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 149 print(f"{property_}: {getattr(self, property_)}") 150 151 def do_query(self, query: str): 152 """Execute a query against the current database.""" 153 print(f"Executing {query}") 154 with self._DB() as db: 155 results = db.query(query) 156 self.display(results) 157 print(f"{db.cursor.rowcount} affected rows") 158 159 def do_restore(self, file: str): 160 """Replace the current db file with the given db backup file.""" 161 backup = Pathier(file.strip('"')) 162 if not backup.exists(): 163 print(f"{backup} does not exist.") 164 else: 165 print(f"Restoring from {file}...") 166 self.dbpath.write_bytes(backup.read_bytes()) 167 print("Restore complete.") 168 169 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 170 def do_scan(self, args: argshell.Namespace): 171 """Scan the current working directory for database files.""" 172 dbs = self._scan(args.extensions, args.recursive) 173 for db in dbs: 174 print(db.separate(Pathier.cwd().stem)) 175 176 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 177 def do_select(self, args: argshell.Namespace): 178 """Execute a SELECT query with the given args.""" 179 print(f"Querying {args.table}... ") 180 with self._DB() as db: 181 rows = db.select( 182 table=args.table, 183 columns=args.columns, 184 joins=args.joins, 185 where=args.where, 186 group_by=args.group_by, 187 having=args.Having, 188 order_by=args.order_by, 189 limit=args.limit, 190 ) 191 print(f"Found {len(rows)} rows:") 192 self.display(rows) 193 print(f"{len(rows)} rows from {args.table}") 194 195 def do_set_connection_timeout(self, seconds: str): 196 """Set database connection timeout to this number of seconds.""" 197 self.connection_timeout = float(seconds) 198 199 def do_set_detect_types(self, should_detect: str): 200 """Pass a `1` to turn on and a `0` to turn off.""" 201 self.detect_types = bool(int(should_detect)) 202 203 def do_set_enforce_foreign_keys(self, should_enforce: str): 204 """Pass a `1` to turn on and a `0` to turn off.""" 205 self.enforce_foreign_keys = bool(int(should_enforce)) 206 207 def do_size(self, _: str): 208 """Display the size of the the current db file.""" 209 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 210 211 @argshell.with_parser(dbparsers.get_update_parser) 212 def do_update(self, args: argshell.Namespace): 213 """Update a column to a new value. 214 215 Syntax: 216 >>> update {table} {column} {value} {where} 217 >>> based>update users username big_chungus "username = lil_chungus" 218 219 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 220 """ 221 print("Updating rows...") 222 with self._DB() as db: 223 num_updates = db.update(args.table, args.column, args.new_value, args.where) 224 print(f"Updated {num_updates} rows in table {args.table}.") 225 226 def do_use(self, arg: str): 227 """Set which database file to use.""" 228 dbpath = Pathier(arg) 229 if not dbpath.exists(): 230 print(f"{dbpath} does not exist.") 231 print(f"Still using {self.dbpath}") 232 elif not dbpath.is_file(): 233 print(f"{dbpath} is not a file.") 234 print(f"Still using {self.dbpath}") 235 else: 236 self.dbpath = dbpath 237 self.prompt = f"{self.dbpath.name}>" 238 239 def do_vacuum(self, _: str): 240 """Reduce database disk memory.""" 241 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 242 print("Vacuuming database...") 243 with self._DB() 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 # Seat 249 250 def _choose_db(self, options: list[Pathier]) -> Pathier: 251 """Prompt the user to select from a list of files.""" 252 cwd = Pathier.cwd() 253 paths = [path.separate(cwd.stem) for path in options] 254 while True: 255 print( 256 f"DB options:\n{' '.join([f'({i}) {path}' for i, path in enumerate(paths, 1)])}" 257 ) 258 choice = input("Enter the number of the option to use: ") 259 try: 260 index = int(choice) 261 if not 1 <= index <= len(options): 262 print("Choice out of range.") 263 continue 264 return options[index - 1] 265 except Exception as e: 266 print(f"{choice} is not a valid option.") 267 268 def _scan( 269 self, extensions: list[str] = [".sqlite3", ".db"], recursive: bool = False 270 ) -> list[Pathier]: 271 cwd = Pathier.cwd() 272 dbs = [] 273 globber = cwd.glob 274 if recursive: 275 globber = cwd.rglob 276 for extension in extensions: 277 dbs.extend(list(globber(f"*{extension}"))) 278 return dbs 279 280 def preloop(self): 281 """Scan the current directory for a .db file to use. 282 If not found, prompt the user for one or to try again recursively.""" 283 if self.dbpath: 284 self.dbpath = Pathier(self.dbpath) 285 print(f"Defaulting to database {self.dbpath}") 286 else: 287 print("Searching for database...") 288 cwd = Pathier.cwd() 289 dbs = self._scan() 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(f"Could not find a .db file in {cwd}.") 297 path = input( 298 "Enter path to .db file to use or press enter to search again recursively: " 299 ) 300 if path: 301 self.dbpath = Pathier(path) 302 elif not path: 303 print("Searching recursively...") 304 dbs = self._scan(recursive=True) 305 if len(dbs) == 1: 306 self.dbpath = dbs[0] 307 print(f"Using database {self.dbpath}.") 308 elif dbs: 309 self.dbpath = self._choose_db(dbs) 310 else: 311 print("Could not find a .db file.") 312 self.dbpath = Pathier(input("Enter path to a .db file: ")) 313 if not self.dbpath.exists(): 314 raise FileNotFoundError(f"{self.dbpath} does not exist.") 315 if not self.dbpath.is_file(): 316 raise ValueError(f"{self.dbpath} is not a file.") 317 318 319def main(): 320 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 @argshell.with_parser(dbparsers.get_schema_parser) 132 def do_schema(self, args: argshell.Namespace): 133 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 134 print("Getting database schema...") 135 with self._DB() as db: 136 tables = args.tables or db.tables 137 info = [ 138 { 139 "Table Name": table, 140 "Columns": ", ".join(db.get_columns(table)), 141 "Number of Rows": db.count(table) if args.rowcount else "n/a", 142 } 143 for table in tables 144 ] 145 self.display(info) 146 147 def do_properties(self, _: str): 148 """See current database property settings.""" 149 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 150 print(f"{property_}: {getattr(self, property_)}") 151 152 def do_query(self, query: str): 153 """Execute a query against the current database.""" 154 print(f"Executing {query}") 155 with self._DB() as db: 156 results = db.query(query) 157 self.display(results) 158 print(f"{db.cursor.rowcount} affected rows") 159 160 def do_restore(self, file: str): 161 """Replace the current db file with the given db backup file.""" 162 backup = Pathier(file.strip('"')) 163 if not backup.exists(): 164 print(f"{backup} does not exist.") 165 else: 166 print(f"Restoring from {file}...") 167 self.dbpath.write_bytes(backup.read_bytes()) 168 print("Restore complete.") 169 170 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 171 def do_scan(self, args: argshell.Namespace): 172 """Scan the current working directory for database files.""" 173 dbs = self._scan(args.extensions, args.recursive) 174 for db in dbs: 175 print(db.separate(Pathier.cwd().stem)) 176 177 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 178 def do_select(self, args: argshell.Namespace): 179 """Execute a SELECT query with the given args.""" 180 print(f"Querying {args.table}... ") 181 with self._DB() as db: 182 rows = db.select( 183 table=args.table, 184 columns=args.columns, 185 joins=args.joins, 186 where=args.where, 187 group_by=args.group_by, 188 having=args.Having, 189 order_by=args.order_by, 190 limit=args.limit, 191 ) 192 print(f"Found {len(rows)} rows:") 193 self.display(rows) 194 print(f"{len(rows)} rows from {args.table}") 195 196 def do_set_connection_timeout(self, seconds: str): 197 """Set database connection timeout to this number of seconds.""" 198 self.connection_timeout = float(seconds) 199 200 def do_set_detect_types(self, should_detect: str): 201 """Pass a `1` to turn on and a `0` to turn off.""" 202 self.detect_types = bool(int(should_detect)) 203 204 def do_set_enforce_foreign_keys(self, should_enforce: str): 205 """Pass a `1` to turn on and a `0` to turn off.""" 206 self.enforce_foreign_keys = bool(int(should_enforce)) 207 208 def do_size(self, _: str): 209 """Display the size of the the current db file.""" 210 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.") 211 212 @argshell.with_parser(dbparsers.get_update_parser) 213 def do_update(self, args: argshell.Namespace): 214 """Update a column to a new value. 215 216 Syntax: 217 >>> update {table} {column} {value} {where} 218 >>> based>update users username big_chungus "username = lil_chungus" 219 220 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 221 """ 222 print("Updating rows...") 223 with self._DB() as db: 224 num_updates = db.update(args.table, args.column, args.new_value, args.where) 225 print(f"Updated {num_updates} rows in table {args.table}.") 226 227 def do_use(self, arg: str): 228 """Set which database file to use.""" 229 dbpath = Pathier(arg) 230 if not dbpath.exists(): 231 print(f"{dbpath} does not exist.") 232 print(f"Still using {self.dbpath}") 233 elif not dbpath.is_file(): 234 print(f"{dbpath} is not a file.") 235 print(f"Still using {self.dbpath}") 236 else: 237 self.dbpath = dbpath 238 self.prompt = f"{self.dbpath.name}>" 239 240 def do_vacuum(self, _: str): 241 """Reduce database disk memory.""" 242 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 243 print("Vacuuming database...") 244 with self._DB() as db: 245 freedspace = db.vacuum() 246 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 247 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.") 248 249 # Seat 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 _scan( 270 self, extensions: list[str] = [".sqlite3", ".db"], recursive: bool = False 271 ) -> list[Pathier]: 272 cwd = Pathier.cwd() 273 dbs = [] 274 globber = cwd.glob 275 if recursive: 276 globber = cwd.rglob 277 for extension in extensions: 278 dbs.extend(list(globber(f"*{extension}"))) 279 return dbs 280 281 def preloop(self): 282 """Scan the current directory for a .db file to use. 283 If not found, prompt the user for one or to try again recursively.""" 284 if self.dbpath: 285 self.dbpath = Pathier(self.dbpath) 286 print(f"Defaulting to database {self.dbpath}") 287 else: 288 print("Searching for database...") 289 cwd = Pathier.cwd() 290 dbs = self._scan() 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(f"Could not find a .db file in {cwd}.") 298 path = input( 299 "Enter path to .db file to use or press enter to search again recursively: " 300 ) 301 if path: 302 self.dbpath = Pathier(path) 303 elif not path: 304 print("Searching recursively...") 305 dbs = self._scan(recursive=True) 306 if len(dbs) == 1: 307 self.dbpath = dbs[0] 308 print(f"Using database {self.dbpath}.") 309 elif dbs: 310 self.dbpath = self._choose_db(dbs) 311 else: 312 print("Could not find a .db file.") 313 self.dbpath = Pathier(input("Enter path to a .db file: ")) 314 if not self.dbpath.exists(): 315 raise FileNotFoundError(f"{self.dbpath} does not exist.") 316 if not self.dbpath.is_file(): 317 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 @argshell.with_parser(dbparsers.get_schema_parser) 132 def do_schema(self, args: argshell.Namespace): 133 """Print out the names of the database tables, their columns, and, optionally, the number of rows.""" 134 print("Getting database schema...") 135 with self._DB() as db: 136 tables = args.tables or db.tables 137 info = [ 138 { 139 "Table Name": table, 140 "Columns": ", ".join(db.get_columns(table)), 141 "Number of Rows": db.count(table) if args.rowcount else "n/a", 142 } 143 for table in tables 144 ] 145 self.display(info)
Print out the names of the database tables, their columns, and, optionally, the number of rows.
147 def do_properties(self, _: str): 148 """See current database property settings.""" 149 for property_ in ["connection_timeout", "detect_types", "enforce_foreign_keys"]: 150 print(f"{property_}: {getattr(self, property_)}")
See current database property settings.
152 def do_query(self, query: str): 153 """Execute a query against the current database.""" 154 print(f"Executing {query}") 155 with self._DB() as db: 156 results = db.query(query) 157 self.display(results) 158 print(f"{db.cursor.rowcount} affected rows")
Execute a query against the current database.
160 def do_restore(self, file: str): 161 """Replace the current db file with the given db backup file.""" 162 backup = Pathier(file.strip('"')) 163 if not backup.exists(): 164 print(f"{backup} does not exist.") 165 else: 166 print(f"Restoring from {file}...") 167 self.dbpath.write_bytes(backup.read_bytes()) 168 print("Restore complete.")
Replace the current db file with the given db backup file.
170 @argshell.with_parser(dbparsers.get_scan_dbs_parser) 171 def do_scan(self, args: argshell.Namespace): 172 """Scan the current working directory for database files.""" 173 dbs = self._scan(args.extensions, args.recursive) 174 for db in dbs: 175 print(db.separate(Pathier.cwd().stem))
Scan the current working directory for database files.
177 @argshell.with_parser(dbparsers.get_select_parser, [dbparsers.select_post_parser]) 178 def do_select(self, args: argshell.Namespace): 179 """Execute a SELECT query with the given args.""" 180 print(f"Querying {args.table}... ") 181 with self._DB() as db: 182 rows = db.select( 183 table=args.table, 184 columns=args.columns, 185 joins=args.joins, 186 where=args.where, 187 group_by=args.group_by, 188 having=args.Having, 189 order_by=args.order_by, 190 limit=args.limit, 191 ) 192 print(f"Found {len(rows)} rows:") 193 self.display(rows) 194 print(f"{len(rows)} rows from {args.table}")
Execute a SELECT query with the given args.
196 def do_set_connection_timeout(self, seconds: str): 197 """Set database connection timeout to this number of seconds.""" 198 self.connection_timeout = float(seconds)
Set database connection timeout to this number of seconds.
200 def do_set_detect_types(self, should_detect: str): 201 """Pass a `1` to turn on and a `0` to turn off.""" 202 self.detect_types = bool(int(should_detect))
Pass a 1
to turn on and a 0
to turn off.
204 def do_set_enforce_foreign_keys(self, should_enforce: str): 205 """Pass a `1` to turn on and a `0` to turn off.""" 206 self.enforce_foreign_keys = bool(int(should_enforce))
Pass a 1
to turn on and a 0
to turn off.
208 def do_size(self, _: str): 209 """Display the size of the the current db file.""" 210 print(f"{self.dbpath.name} is {self.dbpath.formatted_size}.")
Display the size of the the current db file.
212 @argshell.with_parser(dbparsers.get_update_parser) 213 def do_update(self, args: argshell.Namespace): 214 """Update a column to a new value. 215 216 Syntax: 217 >>> update {table} {column} {value} {where} 218 >>> based>update users username big_chungus "username = lil_chungus" 219 220 ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^ 221 """ 222 print("Updating rows...") 223 with self._DB() as db: 224 num_updates = db.update(args.table, args.column, args.new_value, args.where) 225 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'^
227 def do_use(self, arg: str): 228 """Set which database file to use.""" 229 dbpath = Pathier(arg) 230 if not dbpath.exists(): 231 print(f"{dbpath} does not exist.") 232 print(f"Still using {self.dbpath}") 233 elif not dbpath.is_file(): 234 print(f"{dbpath} is not a file.") 235 print(f"Still using {self.dbpath}") 236 else: 237 self.dbpath = dbpath 238 self.prompt = f"{self.dbpath.name}>"
Set which database file to use.
240 def do_vacuum(self, _: str): 241 """Reduce database disk memory.""" 242 print(f"Database size before vacuuming: {self.dbpath.formatted_size}") 243 print("Vacuuming database...") 244 with self._DB() as db: 245 freedspace = db.vacuum() 246 print(f"Database size after vacuuming: {self.dbpath.formatted_size}") 247 print(f"Freed up {Pathier.format_bytes(freedspace)} of disk space.")
Reduce database disk memory.
281 def preloop(self): 282 """Scan the current directory for a .db file to use. 283 If not found, prompt the user for one or to try again recursively.""" 284 if self.dbpath: 285 self.dbpath = Pathier(self.dbpath) 286 print(f"Defaulting to database {self.dbpath}") 287 else: 288 print("Searching for database...") 289 cwd = Pathier.cwd() 290 dbs = self._scan() 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(f"Could not find a .db file in {cwd}.") 298 path = input( 299 "Enter path to .db file to use or press enter to search again recursively: " 300 ) 301 if path: 302 self.dbpath = Pathier(path) 303 elif not path: 304 print("Searching recursively...") 305 dbs = self._scan(recursive=True) 306 if len(dbs) == 1: 307 self.dbpath = dbs[0] 308 print(f"Using database {self.dbpath}.") 309 elif dbs: 310 self.dbpath = self._choose_db(dbs) 311 else: 312 print("Could not find a .db file.") 313 self.dbpath = Pathier(input("Enter path to a .db file: ")) 314 if not self.dbpath.exists(): 315 raise FileNotFoundError(f"{self.dbpath} does not exist.") 316 if not self.dbpath.is_file(): 317 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