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_create_table(self, args: argshell.Namespace):
 42        """Create a table."""
 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_find(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    def do_flush_log(self, arg: str):
186        """Clear the log file for this database."""
187        log_path = self.dbpath.with_name(self.dbpath.stem + "db.log")
188        if not log_path.exists():
189            print(f"No log file at path {log_path}")
190        else:
191            print(f"Flushing log...")
192            log_path.write_text("")
193
194    def do_customize(self, arg: str):
195        """Generate a template file in the current working directory for creating a custom DBManager class.
196        Expects one argument: the name of the custom dbmanager.
197        This will be used to name the generated file as well as several components in the file content."""
198        custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py")
199        if custom_file.exists():
200            print(f"Error: {custom_file.name} already exists in this location.")
201        else:
202            variable_name = "_".join(word for word in arg.lower().split())
203            class_name = "".join(word.capitalize() for word in arg.split())
204            content = (Pathier(__file__).parent / "custom_manager.py").read_text()
205            content = content.replace("CustomManager", class_name)
206            content = content.replace("custommanager", variable_name)
207            custom_file.write_text(content)
208
209    def _choose_db(self, options: list[Pathier]) -> Pathier:
210        """Prompt the user to select from a list of files."""
211        cwd = Pathier.cwd()
212        paths = [path.separate(cwd.stem) for path in options]
213        while True:
214            print(
215                f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}"
216            )
217            choice = input("Enter the number of the option to use: ")
218            try:
219                index = int(choice)
220                if not 1 <= index <= len(options):
221                    print("Choice out of range.")
222                    continue
223                return options[index - 1]
224            except Exception as e:
225                print(f"{choice} is not a valid option.")
226
227    def preloop(self):
228        """Scan the current directory for a .db file to use.
229        If not found, prompt the user for one or to try again recursively."""
230        if self.dbpath:
231            self.dbpath = Pathier(self.dbpath)
232            print(f"Defaulting to database {self.dbpath}")
233        else:
234            print("Searching for database...")
235            cwd = Pathier.cwd()
236            dbs = list(cwd.glob("*.db"))
237            if len(dbs) == 1:
238                self.dbpath = dbs[0]
239                print(f"Using database {self.dbpath}.")
240            elif dbs:
241                self.dbpath = self._choose_db(dbs)
242            else:
243                print(f"Could not find a .db file in {cwd}.")
244                path = input(
245                    "Enter path to .db file to use or press enter to search again recursively: "
246                )
247                if path:
248                    self.dbpath = Pathier(path)
249                elif not path:
250                    print("Searching recursively...")
251                    dbs = list(cwd.rglob("*.db"))
252                    if len(dbs) == 1:
253                        self.dbpath = dbs[0]
254                        print(f"Using database {self.dbpath}.")
255                    elif dbs:
256                        self.dbpath = self._choose_db(dbs)
257                    else:
258                        print("Could not find a .db file.")
259                        self.dbpath = Pathier(input("Enter path to a .db file: "))
260        if not self.dbpath.exists():
261            raise FileNotFoundError(f"{self.dbpath} does not exist.")
262        if not self.dbpath.is_file():
263            raise ValueError(f"{self.dbpath} is not a file.")
264
265
266def main():
267    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_create_table(self, args: argshell.Namespace):
 43        """Create a table."""
 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_find(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    def do_flush_log(self, arg: str):
187        """Clear the log file for this database."""
188        log_path = self.dbpath.with_name(self.dbpath.stem + "db.log")
189        if not log_path.exists():
190            print(f"No log file at path {log_path}")
191        else:
192            print(f"Flushing log...")
193            log_path.write_text("")
194
195    def do_customize(self, arg: str):
196        """Generate a template file in the current working directory for creating a custom DBManager class.
197        Expects one argument: the name of the custom dbmanager.
198        This will be used to name the generated file as well as several components in the file content."""
199        custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py")
200        if custom_file.exists():
201            print(f"Error: {custom_file.name} already exists in this location.")
202        else:
203            variable_name = "_".join(word for word in arg.lower().split())
204            class_name = "".join(word.capitalize() for word in arg.split())
205            content = (Pathier(__file__).parent / "custom_manager.py").read_text()
206            content = content.replace("CustomManager", class_name)
207            content = content.replace("custommanager", variable_name)
208            custom_file.write_text(content)
209
210    def _choose_db(self, options: list[Pathier]) -> Pathier:
211        """Prompt the user to select from a list of files."""
212        cwd = Pathier.cwd()
213        paths = [path.separate(cwd.stem) for path in options]
214        while True:
215            print(
216                f"DB options:\n{' '.join([f'({i}) {path}' for i,path in enumerate(paths,1)])}"
217            )
218            choice = input("Enter the number of the option to use: ")
219            try:
220                index = int(choice)
221                if not 1 <= index <= len(options):
222                    print("Choice out of range.")
223                    continue
224                return options[index - 1]
225            except Exception as e:
226                print(f"{choice} is not a valid option.")
227
228    def preloop(self):
229        """Scan the current directory for a .db file to use.
230        If not found, prompt the user for one or to try again recursively."""
231        if self.dbpath:
232            self.dbpath = Pathier(self.dbpath)
233            print(f"Defaulting to database {self.dbpath}")
234        else:
235            print("Searching for database...")
236            cwd = Pathier.cwd()
237            dbs = list(cwd.glob("*.db"))
238            if len(dbs) == 1:
239                self.dbpath = dbs[0]
240                print(f"Using database {self.dbpath}.")
241            elif dbs:
242                self.dbpath = self._choose_db(dbs)
243            else:
244                print(f"Could not find a .db file in {cwd}.")
245                path = input(
246                    "Enter path to .db file to use or press enter to search again recursively: "
247                )
248                if path:
249                    self.dbpath = Pathier(path)
250                elif not path:
251                    print("Searching recursively...")
252                    dbs = list(cwd.rglob("*.db"))
253                    if len(dbs) == 1:
254                        self.dbpath = dbs[0]
255                        print(f"Using database {self.dbpath}.")
256                    elif dbs:
257                        self.dbpath = self._choose_db(dbs)
258                    else:
259                        print("Could not find a .db file.")
260                        self.dbpath = Pathier(input("Enter path to a .db file: "))
261        if not self.dbpath.exists():
262            raise FileNotFoundError(f"{self.dbpath} does not exist.")
263        if not self.dbpath.is_file():
264            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_create_table(self, args: argshell.argshell.Namespace):
41    @argshell.with_parser(dbparsers.get_create_table_parser)
42    def do_create_table(self, args: argshell.Namespace):
43        """Create a table."""
44        with DataBased(self.dbpath) as db:
45            db.create_table(args.table_name, args.columns)

Create a table.

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_find(self, args: argshell.argshell.Namespace):
 77    @argshell.with_parser(dbparsers.get_lookup_parser, [dbparsers.convert_match_pairs])
 78    def do_find(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'^

def do_flush_log(self, arg: str):
186    def do_flush_log(self, arg: str):
187        """Clear the log file for this database."""
188        log_path = self.dbpath.with_name(self.dbpath.stem + "db.log")
189        if not log_path.exists():
190            print(f"No log file at path {log_path}")
191        else:
192            print(f"Flushing log...")
193            log_path.write_text("")

Clear the log file for this database.

def do_customize(self, arg: str):
195    def do_customize(self, arg: str):
196        """Generate a template file in the current working directory for creating a custom DBManager class.
197        Expects one argument: the name of the custom dbmanager.
198        This will be used to name the generated file as well as several components in the file content."""
199        custom_file = (Pathier.cwd() / arg.replace(" ", "_")).with_suffix(".py")
200        if custom_file.exists():
201            print(f"Error: {custom_file.name} already exists in this location.")
202        else:
203            variable_name = "_".join(word for word in arg.lower().split())
204            class_name = "".join(word.capitalize() for word in arg.split())
205            content = (Pathier(__file__).parent / "custom_manager.py").read_text()
206            content = content.replace("CustomManager", class_name)
207            content = content.replace("custommanager", variable_name)
208            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):
228    def preloop(self):
229        """Scan the current directory for a .db file to use.
230        If not found, prompt the user for one or to try again recursively."""
231        if self.dbpath:
232            self.dbpath = Pathier(self.dbpath)
233            print(f"Defaulting to database {self.dbpath}")
234        else:
235            print("Searching for database...")
236            cwd = Pathier.cwd()
237            dbs = list(cwd.glob("*.db"))
238            if len(dbs) == 1:
239                self.dbpath = dbs[0]
240                print(f"Using database {self.dbpath}.")
241            elif dbs:
242                self.dbpath = self._choose_db(dbs)
243            else:
244                print(f"Could not find a .db file in {cwd}.")
245                path = input(
246                    "Enter path to .db file to use or press enter to search again recursively: "
247                )
248                if path:
249                    self.dbpath = Pathier(path)
250                elif not path:
251                    print("Searching recursively...")
252                    dbs = list(cwd.rglob("*.db"))
253                    if len(dbs) == 1:
254                        self.dbpath = dbs[0]
255                        print(f"Using database {self.dbpath}.")
256                    elif dbs:
257                        self.dbpath = self._choose_db(dbs)
258                    else:
259                        print("Could not find a .db file.")
260                        self.dbpath = Pathier(input("Enter path to a .db file: "))
261        if not self.dbpath.exists():
262            raise FileNotFoundError(f"{self.dbpath} does not exist.")
263        if not self.dbpath.is_file():
264            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_help
cmdloop
def main():
267def main():
268    DBManager().cmdloop()