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    args = parser.parse_args(command)
129
130    if args.dbname and not Path(args.dbname).exists():
131        raise Exception(f"{args.dbname} does not exist.")
132
133    return args
134
135
136def info():
137    print("Getting database info...")
138    print()
139    if not args.tables:
140        args.tables = db.get_table_names()
141    results = []
142    for table in args.tables:
143        count = db.count(table)
144        columns = db.get_column_names(table)
145        results.append(
146            {
147                "table name": table,
148                "columns": ", ".join(columns),
149                "number of rows": count,
150            }
151        )
152    if args.sort_by and args.sort_by in results[0]:
153        results = sorted(results, key=lambda x: x[args.sort_by])
154    print(data_to_string(results))
155
156
157def find():
158    print("Finding records... ")
159    print()
160    if not args.tables:
161        args.tables = db.get_table_names()
162    for table in args.tables:
163        results = db.find(table, args.find, args.columns)
164        if args.sort_by and args.sort_by in results[0]:
165            results = sorted(results, key=lambda x: x[args.sort_by])
166        if args.columns:
167            print(
168                f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:"
169            )
170        else:
171            print(f"{len(results)} results for '{args.find}' in '{table}' table:")
172        if not args.show_count_only:
173            print(data_to_string(results))
174        print()
175
176
177def delete():
178    if not args.tables:
179        raise ValueError("Missing -t/--tables arg for -d/--delete function.")
180    if not args.columns:
181        raise ValueError("Missing -c/--columns arg for -d/--delete function.")
182    print("Deleting records... ")
183    print()
184    num_deleted_records = 0
185    failed_deletions = []
186    for item in args.delete:
187        success = db.delete(args.tables[0], [(args.columns[0], item)])
188        if success:
189            num_deleted_records += success
190        else:
191            failed_deletions.append(item)
192    print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.")
193    if len(failed_deletions) > 0:
194        print(
195            f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:"
196        )
197        for fail in failed_deletions:
198            print(f"  {fail}")
199
200
201def update():
202    if not args.tables:
203        raise ValueError("Missing -t/--tables arg for -u/--update function.")
204    if not args.columns:
205        raise ValueError("Missing -c/--columns arg for -u/--update function.")
206    print("Updating record... ")
207    print()
208    new_value = args.update[0]
209    if len(args.update) > 1:
210        args.update = args.update[1:]
211        match_criteria = [
212            (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2)
213        ]
214    else:
215        match_criteria = None
216    if db.update(
217        args.tables[0],
218        args.columns[0],
219        new_value,
220        match_criteria,
221    ):
222        print(
223            f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
224        )
225    else:
226        print(
227            f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
228        )
229
230
231def print_table():
232    for table in args.tables:
233        rows = db.get_rows(
234            table, columns_to_return=args.columns, sort_by_column=args.sort_by
235        )
236        print(f"{table} table:")
237        print(data_to_string(rows))
238
239
240if __name__ == "__main__":
241    sys.tracebacklimit = 0
242    while True:
243        try:
244            command = shlex.split(input("Enter command: "))
245            args = get_args(command)
246            if args.dbname:
247                dbname = args.dbname
248            with DataBased(dbpath=dbname) as db:
249                if args.info:
250                    info()
251                elif args.find:
252                    find()
253                elif args.delete:
254                    delete()
255                elif args.update:
256                    update()
257                else:
258                    print_table()
259        except KeyboardInterrupt:
260            break
261        except Exception as e:
262            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    args = parser.parse_args(command)
130
131    if args.dbname and not Path(args.dbname).exists():
132        raise Exception(f"{args.dbname} does not exist.")
133
134    return args
def info():
137def info():
138    print("Getting database info...")
139    print()
140    if not args.tables:
141        args.tables = db.get_table_names()
142    results = []
143    for table in args.tables:
144        count = db.count(table)
145        columns = db.get_column_names(table)
146        results.append(
147            {
148                "table name": table,
149                "columns": ", ".join(columns),
150                "number of rows": count,
151            }
152        )
153    if args.sort_by and args.sort_by in results[0]:
154        results = sorted(results, key=lambda x: x[args.sort_by])
155    print(data_to_string(results))
def find():
158def find():
159    print("Finding records... ")
160    print()
161    if not args.tables:
162        args.tables = db.get_table_names()
163    for table in args.tables:
164        results = db.find(table, args.find, args.columns)
165        if args.sort_by and args.sort_by in results[0]:
166            results = sorted(results, key=lambda x: x[args.sort_by])
167        if args.columns:
168            print(
169                f"{len(results)} results for '{args.find}' in '{', '.join(args.columns)}' column(s) of '{table}' table:"
170            )
171        else:
172            print(f"{len(results)} results for '{args.find}' in '{table}' table:")
173        if not args.show_count_only:
174            print(data_to_string(results))
175        print()
def delete():
178def delete():
179    if not args.tables:
180        raise ValueError("Missing -t/--tables arg for -d/--delete function.")
181    if not args.columns:
182        raise ValueError("Missing -c/--columns arg for -d/--delete function.")
183    print("Deleting records... ")
184    print()
185    num_deleted_records = 0
186    failed_deletions = []
187    for item in args.delete:
188        success = db.delete(args.tables[0], [(args.columns[0], item)])
189        if success:
190            num_deleted_records += success
191        else:
192            failed_deletions.append(item)
193    print(f"Deleted {num_deleted_records} record(s) from '{args.tables[0]}' table.")
194    if len(failed_deletions) > 0:
195        print(
196            f"Failed to delete the following {len(failed_deletions)} record(s) from '{args.tables[0]}' table:"
197        )
198        for fail in failed_deletions:
199            print(f"  {fail}")
def update():
202def update():
203    if not args.tables:
204        raise ValueError("Missing -t/--tables arg for -u/--update function.")
205    if not args.columns:
206        raise ValueError("Missing -c/--columns arg for -u/--update function.")
207    print("Updating record... ")
208    print()
209    new_value = args.update[0]
210    if len(args.update) > 1:
211        args.update = args.update[1:]
212        match_criteria = [
213            (args.update[i], args.update[i + 1]) for i in range(0, len(args.update), 2)
214        ]
215    else:
216        match_criteria = None
217    if db.update(
218        args.tables[0],
219        args.columns[0],
220        new_value,
221        match_criteria,
222    ):
223        print(
224            f"Updated '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
225        )
226    else:
227        print(
228            f"Failed to update '{args.columns[0]}' column to '{new_value}' in '{args.tables[0]}' table for match_criteria {match_criteria}."
229        )