databased.dbmanager

  1import argparse
  2import shlex
  3import sys
  4from pathlib import Path
  5
  6from databased import DataBased, data_to_string
  7
  8""" A command line tool to interact with a database file.
  9Works like a standard argparse based cli
 10except it will ask you for arguments indefinitely in a loop
 11instead of having to invoke the script with arguments over and over.
 12I.e. instead of "python dbManager.py -db database.db -f someString -t someTable",
 13just invoke "python dbManager.py" then a repeating "Enter command: " prompt will appear
 14and "-db database.db -f someString -t someTable" can be entered.
 15Note: a -db arg only needs to be provided once (if there is no default set) unless
 16you wish to change databases. So a subsequent command to the above
 17can just be entered as "-f someOtherString -t someTable".
 18
 19This is just a quick template and can be customized by adding arguments and adding/overriding functions
 20for specific database projects."""
 21
 22# Subclassing to prevent program exit when the -h/--help arg is passed.
 23class ArgParser(argparse.ArgumentParser):
 24    def exit(self, status=0, message=None):
 25        if message:
 26            self._print_message(message, sys.stderr)
 27
 28
 29def get_args(command: str) -> argparse.Namespace:
 30    parser = ArgParser()
 31
 32    parser.add_argument(
 33        "-db",
 34        "--dbname",
 35        type=str,
 36        default=None,
 37        help="""Name of database file to use.
 38        Required on the first loop if no default is set,
 39        but subsequent loops will resuse the same database
 40        unless a new one is provided through this arg.""",
 41    )
 42
 43    parser.add_argument(
 44        "-i",
 45        "--info",
 46        action="store_true",
 47        help=""" Display table names, their respective columns, and how many records they contain.
 48        If a -t/--tables arg is passed, just the columns and row count for those tables will be shown.""",
 49    )
 50
 51    parser.add_argument(
 52        "-t",
 53        "--tables",
 54        type=str,
 55        nargs="*",
 56        default=[],
 57        help="""Limits commands to a specific list of tables.
 58        Optional for some commands, required for others.
 59        If this is the only arg given (besides -db if not already set),
 60        the whole table will be printed to the terminal.""",
 61    )
 62
 63    parser.add_argument(
 64        "-c",
 65        "--columns",
 66        type=str,
 67        nargs="*",
 68        default=[],
 69        help=""" Limits commands to a specific list of columns.
 70        Optional for some commands, required for others.
 71        If this and -t are the only args given 
 72        (besides -db if not already set), the whole table will be printed
 73        to the terminal, but with only the columns provided with this arg.""",
 74    )
 75
 76    parser.add_argument(
 77        "-f",
 78        "--find",
 79        type=str,
 80        default=None,
 81        help=""" A substring to search the database for. 
 82        If a -c/--columns arg(s) is not given, the values will be matched against all columns.
 83        Similarly, if a -t/--tables arg(s) is not given, the values will be searched for in all tables.""",
 84    )
 85
 86    parser.add_argument(
 87        "-sco",
 88        "--show_count_only",
 89        action="store_true",
 90        help=""" Show the number of results returned by -f/--find,
 91        but don't print the results to the terminal.""",
 92    )
 93
 94    parser.add_argument(
 95        "-d",
 96        "--delete",
 97        type=str,
 98        nargs="*",
 99        default=[],
100        help=""" A list of values to be deleted from the database.
101        A -c/--columns arg must be supplied.
102        A -t/--tables arg must be supplied.""",
103    )
104
105    parser.add_argument(
106        "-u",
107        "--update",
108        type=str,
109        default=None,
110        nargs="*",
111        help=""" Update a record in the database.
112        Expects the first argument to be the new value and interprets
113        subsequent arguements as pairs of 'column' and 'value' to use
114        when selecting which rows to update. The -c/--columns arg will
115        be the column that is updated with the new value for matching rows.
116        A -c/--columns arg must be supplied.
117        A -t/--tables arg must be supplied.
118        e.g '-t birds -c last_seen -u today name sparrow migratory 0'
119        will update the 'last_seen' column of the 'birds' table to 'today'
120        for all rows that have either/both of their 'name' and 'migratory'
121        columns set to 'sparrow' and '0', respectively.""",
122    )
123
124    parser.add_argument(
125        "-sb", "--sort_by", type=str, default=None, help="Column to sort results by."
126    )
127
128    parser.add_argument(
129        "-q",
130        "--query",
131        type=str,
132        default=None,
133        help=""" Directly execute a query against the database. """,
134    )
135
136    args = parser.parse_args(command)
137
138    if args.dbname and not Path(args.dbname).exists():
139        raise Exception(f"{args.dbname} does not exist.")
140
141    return args
142
143
144def info():
145    print("Getting database info...")
146    print()
147    if not args.tables:
148        args.tables = db.get_table_names()
149    results = []
150    for table in args.tables:
151        count = db.count(table)
152        columns = db.get_column_names(table)
153        results.append(
154            {
155                "table name": table,
156                "columns": ", ".join(columns),
157                "number of rows": count,
158            }
159        )
160    if args.sort_by and args.sort_by in results[0]:
161        results = sorted(results, key=lambda x: x[args.sort_by])
162    print(data_to_string(results))
163
164
165def find():
166    print("Finding records... ")
167    print()
168    if not args.tables:
169        args.tables = db.get_table_names()
170    for table in args.tables:
171        results = db.find(table, args.find, args.columns)
172        if args.sort_by and args.sort_by in results[0]:
173            results = sorted(results, key=lambda x: x[args.sort_by])
174        if args.columns:
175            print(
176                f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:"
177            )
178        else:
179            print(f"{len(results)} results for '{args.find}' in '{table}' table:")
180        if not args.show_count_only:
181            print(data_to_string(results))
182        print()
183
184
185def delete():
186    if not args.tables:
187        raise ValueError("Missing -t/--tables arg for -d/--delete function.")
188    if not args.columns:
189        raise ValueError("Missing -c/--columns arg for -d/--delete function.")
190    print("Deleting records... ")
191    print()
192    num_deleted_records = 0
193    failed_deletions = []
194    for item in args.delete:
195        success = db.delete(args.tables[0], [(args.columns[0], item)])
196        if success:
197            num_deleted_records += success
198        else:
199            failed_deletions.append(item)
200    print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.")
201    if len(failed_deletions) > 0:
202        print(
203            f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:"
204        )
205        for fail in failed_deletions:
206            print(f"  {fail}")
207
208
209def update():
210    if not args.tables:
211        raise ValueError("Missing -t/--tables arg for -u/--update function.")
212    if not args.columns:
213        raise ValueError("Missing -c/--columns arg for -u/--update function.")
214    print("Updating record... ")
215    print()
216    new_value = args.update[0]
217    if len(args.update) > 1:
218        args.update = args.update[1:]
219        match_criteria = [
220            (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2)
221        ]
222    else:
223        match_criteria = None
224    if db.update(
225        args.tables[0],
226        args.columns[0],
227        new_value,
228        match_criteria,
229    ):
230        print(
231            f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
232        )
233    else:
234        print(
235            f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
236        )
237
238
239def print_table():
240    for table in args.tables:
241        rows = db.get_rows(
242            table, columns_to_return=args.columns, sort_by_column=args.sort_by
243        )
244        print(f"{table} table:")
245        print(data_to_string(rows))
246
247
248def query():
249    results = db.query(args.query)
250    try:
251        for result in results:
252            print(*result, sep=" * ")
253    except Exception as e:
254        print(f"Couldn't display results of '{args.query}'.")
255
256
257if __name__ == "__main__":
258    sys.tracebacklimit = 0
259    while True:
260        try:
261            command = shlex.split(input("Enter command: "))
262            args = get_args(command)
263            if args.dbname:
264                dbname = args.dbname
265            with DataBased(dbpath=dbname) as db:
266                if args.info:
267                    info()
268                elif args.find:
269                    find()
270                elif args.delete:
271                    delete()
272                elif args.update:
273                    update()
274                elif args.query:
275                    query()
276                else:
277                    print_table()
278        except KeyboardInterrupt:
279            break
280        except Exception as e:
281            print(e)
class ArgParser(argparse.ArgumentParser):
24class ArgParser(argparse.ArgumentParser):
25    def exit(self, status=0, message=None):
26        if message:
27            self._print_message(message, sys.stderr)

Object for parsing command line strings into Python objects.

Keyword Arguments: - prog -- The name of the program (default: os.path.basename(sys.argv[0])) - usage -- A usage message (default: auto-generated from arguments) - description -- A description of what the program does - epilog -- Text following the argument descriptions - parents -- Parsers whose arguments should be copied into this one - formatter_class -- HelpFormatter class for printing help messages - prefix_chars -- Characters that prefix optional arguments - fromfile_prefix_chars -- Characters that prefix files containing additional arguments - argument_default -- The default value for all arguments - conflict_handler -- String indicating how to handle conflicts - add_help -- Add a -h/-help option - allow_abbrev -- Allow long options to be abbreviated unambiguously - exit_on_error -- Determines whether or not ArgumentParser exits with error info when an error occurs

def exit(self, status=0, message=None):
25    def exit(self, status=0, message=None):
26        if message:
27            self._print_message(message, sys.stderr)
Inherited Members
argparse.ArgumentParser
ArgumentParser
add_subparsers
parse_args
parse_known_args
convert_arg_line_to_args
parse_intermixed_args
parse_known_intermixed_args
format_usage
format_help
print_usage
print_help
error
argparse._ActionsContainer
register
set_defaults
get_default
add_argument
add_argument_group
add_mutually_exclusive_group
def get_args(command: str) -> argparse.Namespace:
 30def get_args(command: str) -> argparse.Namespace:
 31    parser = ArgParser()
 32
 33    parser.add_argument(
 34        "-db",
 35        "--dbname",
 36        type=str,
 37        default=None,
 38        help="""Name of database file to use.
 39        Required on the first loop if no default is set,
 40        but subsequent loops will resuse the same database
 41        unless a new one is provided through this arg.""",
 42    )
 43
 44    parser.add_argument(
 45        "-i",
 46        "--info",
 47        action="store_true",
 48        help=""" Display table names, their respective columns, and how many records they contain.
 49        If a -t/--tables arg is passed, just the columns and row count for those tables will be shown.""",
 50    )
 51
 52    parser.add_argument(
 53        "-t",
 54        "--tables",
 55        type=str,
 56        nargs="*",
 57        default=[],
 58        help="""Limits commands to a specific list of tables.
 59        Optional for some commands, required for others.
 60        If this is the only arg given (besides -db if not already set),
 61        the whole table will be printed to the terminal.""",
 62    )
 63
 64    parser.add_argument(
 65        "-c",
 66        "--columns",
 67        type=str,
 68        nargs="*",
 69        default=[],
 70        help=""" Limits commands to a specific list of columns.
 71        Optional for some commands, required for others.
 72        If this and -t are the only args given 
 73        (besides -db if not already set), the whole table will be printed
 74        to the terminal, but with only the columns provided with this arg.""",
 75    )
 76
 77    parser.add_argument(
 78        "-f",
 79        "--find",
 80        type=str,
 81        default=None,
 82        help=""" A substring to search the database for. 
 83        If a -c/--columns arg(s) is not given, the values will be matched against all columns.
 84        Similarly, if a -t/--tables arg(s) is not given, the values will be searched for in all tables.""",
 85    )
 86
 87    parser.add_argument(
 88        "-sco",
 89        "--show_count_only",
 90        action="store_true",
 91        help=""" Show the number of results returned by -f/--find,
 92        but don't print the results to the terminal.""",
 93    )
 94
 95    parser.add_argument(
 96        "-d",
 97        "--delete",
 98        type=str,
 99        nargs="*",
100        default=[],
101        help=""" A list of values to be deleted from the database.
102        A -c/--columns arg must be supplied.
103        A -t/--tables arg must be supplied.""",
104    )
105
106    parser.add_argument(
107        "-u",
108        "--update",
109        type=str,
110        default=None,
111        nargs="*",
112        help=""" Update a record in the database.
113        Expects the first argument to be the new value and interprets
114        subsequent arguements as pairs of 'column' and 'value' to use
115        when selecting which rows to update. The -c/--columns arg will
116        be the column that is updated with the new value for matching rows.
117        A -c/--columns arg must be supplied.
118        A -t/--tables arg must be supplied.
119        e.g '-t birds -c last_seen -u today name sparrow migratory 0'
120        will update the 'last_seen' column of the 'birds' table to 'today'
121        for all rows that have either/both of their 'name' and 'migratory'
122        columns set to 'sparrow' and '0', respectively.""",
123    )
124
125    parser.add_argument(
126        "-sb", "--sort_by", type=str, default=None, help="Column to sort results by."
127    )
128
129    parser.add_argument(
130        "-q",
131        "--query",
132        type=str,
133        default=None,
134        help=""" Directly execute a query against the database. """,
135    )
136
137    args = parser.parse_args(command)
138
139    if args.dbname and not Path(args.dbname).exists():
140        raise Exception(f"{args.dbname} does not exist.")
141
142    return args
def info():
145def info():
146    print("Getting database info...")
147    print()
148    if not args.tables:
149        args.tables = db.get_table_names()
150    results = []
151    for table in args.tables:
152        count = db.count(table)
153        columns = db.get_column_names(table)
154        results.append(
155            {
156                "table name": table,
157                "columns": ", ".join(columns),
158                "number of rows": count,
159            }
160        )
161    if args.sort_by and args.sort_by in results[0]:
162        results = sorted(results, key=lambda x: x[args.sort_by])
163    print(data_to_string(results))
def find():
166def find():
167    print("Finding records... ")
168    print()
169    if not args.tables:
170        args.tables = db.get_table_names()
171    for table in args.tables:
172        results = db.find(table, args.find, args.columns)
173        if args.sort_by and args.sort_by in results[0]:
174            results = sorted(results, key=lambda x: x[args.sort_by])
175        if args.columns:
176            print(
177                f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:"
178            )
179        else:
180            print(f"{len(results)} results for '{args.find}' in '{table}' table:")
181        if not args.show_count_only:
182            print(data_to_string(results))
183        print()
def delete():
186def delete():
187    if not args.tables:
188        raise ValueError("Missing -t/--tables arg for -d/--delete function.")
189    if not args.columns:
190        raise ValueError("Missing -c/--columns arg for -d/--delete function.")
191    print("Deleting records... ")
192    print()
193    num_deleted_records = 0
194    failed_deletions = []
195    for item in args.delete:
196        success = db.delete(args.tables[0], [(args.columns[0], item)])
197        if success:
198            num_deleted_records += success
199        else:
200            failed_deletions.append(item)
201    print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.")
202    if len(failed_deletions) > 0:
203        print(
204            f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:"
205        )
206        for fail in failed_deletions:
207            print(f"  {fail}")
def update():
210def update():
211    if not args.tables:
212        raise ValueError("Missing -t/--tables arg for -u/--update function.")
213    if not args.columns:
214        raise ValueError("Missing -c/--columns arg for -u/--update function.")
215    print("Updating record... ")
216    print()
217    new_value = args.update[0]
218    if len(args.update) > 1:
219        args.update = args.update[1:]
220        match_criteria = [
221            (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2)
222        ]
223    else:
224        match_criteria = None
225    if db.update(
226        args.tables[0],
227        args.columns[0],
228        new_value,
229        match_criteria,
230    ):
231        print(
232            f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
233        )
234    else:
235        print(
236            f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
237        )
def query():
249def query():
250    results = db.query(args.query)
251    try:
252        for result in results:
253            print(*result, sep=" * ")
254    except Exception as e:
255        print(f"Couldn't display results of '{args.query}'.")