databased.dbmanager

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

Subclass this to create custom ArgShells.

def do_use_db(self, arg: str):
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

Set which database file to use.

def do_dbpath(self, arg: str):
25    def do_dbpath(self, arg: str):
26        """Print the .db file in use."""
27        print(self.dbpath)

Print the .db file in use.

def do_backup(self, arg: str):
29    def do_backup(self, arg: str):
30        """Create a backup of the current db file."""
31        print(f"Creating a back up for {self.dbpath}...")
32        backup_path = self.dbpath.with_stem(f"{self.dbpath.stem}_bckup")
33        self.dbpath.copy(backup_path, True)
34        print("Creating backup is complete.")
35        print(f"Backup path: {backup_path}")

Create a backup of the current db file.

def do_size(self, arg: str):
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)}.")

Display the size of the the current db file.

@argshell.with_parser(dbparsers.get_create_table_parser)
def do_add_table(self, args: argshell.argshell.Namespace):
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)

Add a new table to the database.

def do_drop_table(self, arg: str):
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)

Drop the specified table.

@argshell.with_parser(dbparsers.get_add_row_parser, [dbparsers.verify_matching_length])
def do_add_row(self, args: argshell.argshell.Namespace):
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            db.add_row(args.table_name, args.values, args.columns or None)

Add a row to a table.

def do_info(self, arg: str):
60    def do_info(self, arg: str):
61        """Print out the names of the database tables, their columns, and the number of rows.
62        Pass a space-separated list of table names to only print info for those specific tables,
63        otherwise all tables will be printed."""
64        print("Getting database info...")
65        with DataBased(self.dbpath) as db:
66            tables = arg.split() or db.get_table_names()
67            info = [
68                {
69                    "Table Name": table,
70                    "Columns": ", ".join(db.get_column_names(table)),
71                    "Number of Rows": db.count(table),
72                }
73                for table in tables
74            ]
75        print(DataBased.data_to_string(info))

Print out the names of the database tables, their columns, and the number of rows. Pass a space-separated list of table names to only print info for those specific tables, otherwise all tables will be printed.

@argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
def do_show(self, args: argshell.argshell.Namespace):
 77    @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
 78    def do_show(self, args: argshell.Namespace):
 79        """Find and print rows from the database.
 80        Use the -t/--tables, -m/--match_pairs, and -l/--limit flags to limit the search.
 81        Use the -c/--columns flag to limit what columns are printed.
 82        Use the -o/--order_by flag to order the results.
 83        Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs
 84        Pass -h/--help flag for parser help."""
 85        print("Finding records... ")
 86        if len(args.columns) == 0:
 87            args.columns = None
 88        with DataBased(self.dbpath) as db:
 89            tables = args.tables or db.get_table_names()
 90            for table in tables:
 91                results = db.get_rows(
 92                    table,
 93                    args.match_pairs,
 94                    columns_to_return=args.columns,
 95                    order_by=args.order_by,
 96                    limit=args.limit,
 97                    exact_match=not args.partial_matching,
 98                )
 99                db.close()
100                print(f"{len(results)} matching rows in {table} table:")
101                try:
102                    print(DataBased.data_to_string(results))  # type: ignore
103                except Exception as e:
104                    print("Couldn't fit data into a grid.")
105                    print(*results, sep="\n")
106                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.

@argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
def do_count(self, args: argshell.argshell.Namespace):
127    @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
128    def do_count(self, args: argshell.Namespace):
129        """Print the number of rows in the database.
130        Use the -t/--tables flag to limit results to a specific table(s).
131        Use the -m/--match_pairs flag to limit the results to rows matching these criteria.
132        Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs.
133        Pass -h/--help flag for parser help."""
134        print("Counting rows...")
135        with DataBased(self.dbpath) as db:
136            tables = args.tables or db.get_table_names()
137            for table in tables:
138                num_rows = db.count(table, args.match_pairs, not args.partial_matching)
139                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.

def do_query(self, arg: str):
141    def do_query(self, arg: str):
142        """Execute a query against the current database."""
143        print(f"Executing {arg}")
144        with DataBased(self.dbpath) as db:
145            results = db.query(arg)
146        try:
147            for result in results:
148                print(*result, sep="|-|")
149        except Exception as e:
150            print(f"{type(e).__name__}: {e}")

Execute a query against the current database.

@argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs])
def do_update(self, args: argshell.argshell.Namespace):
152    @argshell.with_parser(dbparsers.get_update_parser, [dbparsers.convert_match_pairs])
153    def do_update(self, args: argshell.Namespace):
154        """Update a column to a new value.
155        Two required args: the column (-c/--column) to update and the value (-v/--value) to update to.
156        Use the -t/--tables flag to limit what tables are updated.
157        Use the -m/--match_pairs flag to specify which rows are updated.
158        >>> based>update -c username -v big_chungus -t users -m username lil_chungus
159
160        ^will update the username in the users 'table' to 'big_chungus' where the username is currently 'lil_chungus'^"""
161        print("Updating rows...")
162        with DataBased(self.dbpath) as db:
163            tables = args.tables or db.get_table_names()
164            for table in tables:
165                if db.update(table, args.column, args.new_value, args.match_pairs):
166                    print(f"Updating rows in {table} table successful.")
167                else:
168                    print(f"Failed to update 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.

>>> 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'^

@argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
def do_delete(self, args: argshell.argshell.Namespace):
170    @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
171    def do_delete(self, args: argshell.Namespace):
172        """Delete rows from the database.
173        Use the -t/--tables flag to limit what tables rows are deleted from.
174        Use the -m/--match_pairs flag to specify which rows are deleted.
175        Use the -p/--partial_matching flag to enable substring matching on -m/--match_pairs.
176        >>> based>delete -t users -m username chungus -p
177
178        ^will delete all rows in the 'users' table whose username contains 'chungus'^"""
179        print("Deleting records...")
180        with DataBased(self.dbpath) as db:
181            tables = args.tables or db.get_table_names()
182            for table in tables:
183                num_rows = db.delete(table, args.match_pairs, not args.partial_matching)
184                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'^

@argshell.with_parser(dbparsers.get_add_column_parser)
def do_add_column(self, args: argshell.argshell.Namespace):
186    @argshell.with_parser(dbparsers.get_add_column_parser)
187    def do_add_column(self, args: argshell.Namespace):
188        """Add a new column to the specified tables."""
189        with DataBased(self.dbpath) as db:
190            tables = args.tables or db.get_table_names()
191            for table in tables:
192                db.add_column(table, args.column_name, args.type, args.default_value)

Add a new column to the specified tables.

def do_flush_log(self, arg: str):
194    def do_flush_log(self, arg: str):
195        """Clear the log file for this database."""
196        log_path = self.dbpath.with_name(self.dbpath.stem + "db.log")
197        if not log_path.exists():
198            print(f"No log file at path {log_path}")
199        else:
200            print(f"Flushing log...")
201            log_path.write_text("")

Clear the log file for this database.

def do_scan_dbs(self, arg: str):
203    def do_scan_dbs(self, arg: str):
204        """Scan the current working directory for `*.db` files and display them.
205
206        If the command is entered as `based>scan_dbs r`, the scan will be performed recursively."""
207        cwd = Pathier.cwd()
208        if arg.strip() == "r":
209            dbs = cwd.rglob("*.db")
210        else:
211            dbs = cwd.glob("*.db")
212        for db in dbs:
213            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.

def do_customize(self, arg: str):
215    def do_customize(self, arg: str):
216        """Generate a template file in the current working directory for creating a custom DBManager class.
217        Expects one argument: the name of the custom dbmanager.
218        This will be used to name the generated file as well as several components in the file content."""
219        custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py")
220        if custom_file.exists():
221            print(f"Error: {custom_file.name} already exists in this location.")
222        else:
223            variable_name = "_".join(word for word in arg.lower().split())
224            class_name = "".join(word.capitalize() for word in arg.split())
225            content = (Pathier(__file__).parent / "custom_manager.py").read_text()
226            content = content.replace("CustomManager", class_name)
227            content = content.replace("custommanager", variable_name)
228            custom_file.write_text(content)

Generate a template file in the current working directory for creating a custom DBManager class. Expects one argument: the name of the custom dbmanager. This will be used to name the generated file as well as several components in the file content.

def preloop(self):
248    def preloop(self):
249        """Scan the current directory for a .db file to use.
250        If not found, prompt the user for one or to try again recursively."""
251        if self.dbpath:
252            self.dbpath = Pathier(self.dbpath)
253            print(f"Defaulting to database {self.dbpath}")
254        else:
255            print("Searching for database...")
256            cwd = Pathier.cwd()
257            dbs = list(cwd.glob("*.db"))
258            if len(dbs) == 1:
259                self.dbpath = dbs[0]
260                print(f"Using database {self.dbpath}.")
261            elif dbs:
262                self.dbpath = self._choose_db(dbs)
263            else:
264                print(f"Could not find a .db file in {cwd}.")
265                path = input(
266                    "Enter path to .db file to use or press enter to search again recursively: "
267                )
268                if path:
269                    self.dbpath = Pathier(path)
270                elif not path:
271                    print("Searching recursively...")
272                    dbs = list(cwd.rglob("*.db"))
273                    if len(dbs) == 1:
274                        self.dbpath = dbs[0]
275                        print(f"Using database {self.dbpath}.")
276                    elif dbs:
277                        self.dbpath = self._choose_db(dbs)
278                    else:
279                        print("Could not find a .db file.")
280                        self.dbpath = Pathier(input("Enter path to a .db file: "))
281        if not self.dbpath.exists():
282            raise FileNotFoundError(f"{self.dbpath} does not exist.")
283        if not self.dbpath.is_file():
284            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
emptyline
default
completedefault
completenames
complete
get_names
complete_help
print_topics
columnize
argshell.argshell.ArgShell
do_quit
do_sys
do_help
cmdloop
def main():
287def main():
288    DBManager().cmdloop()