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