partsdb

electronic parts inventory
git clone https://git.e1e0.net/partsdb.git
Log | Files | Refs | README | LICENSE

commit ea6d85d9023b60bdbc34b32d0ed8ca81366d87b3
parent 697f06426eac30c81defb0bb53ec6b22705a337a
Author: Paco Esteban <paco@e1e0.net>
Date:   Thu, 17 Nov 2022 16:55:44 +0100

linting

Diffstat:
Mpartsdb/database.py | 20++++++++++----------
Mpartsdb/helpers.py | 141+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mpartsdb/octopart.py | 26++++++++++++--------------
Mpartsdb/partsdb.py | 231+++++++++++++++++++++++++++++++++++++++++--------------------------------------
4 files changed, 214 insertions(+), 204 deletions(-)

diff --git a/partsdb/database.py b/partsdb/database.py @@ -81,7 +81,7 @@ GET_IMAGE_QUERY = "SELECT image FROM parts WHERE id = ?" GET_DATASHEET_QUERY = "SELECT datasheet FROM parts WHERE id = ?" -class PartsDB(): +class PartsDB: def __init__(self, database): try: self.conn = sqlite3.connect(database) @@ -120,12 +120,12 @@ class PartsDB(): def get_part(self, part_id): c = self.conn.cursor() - c.execute(GET_PART_QUERY, (part_id, )) + c.execute(GET_PART_QUERY, (part_id,)) return c.fetchone() def get_part_by_mpn(self, mpn): c = self.conn.cursor() - c.execute(GET_PART_BY_MPN_QUERY, (mpn, )) + c.execute(GET_PART_BY_MPN_QUERY, (mpn,)) return c.fetchone() def search_parts(self, term): @@ -135,7 +135,7 @@ class PartsDB(): def get_part_history(self, part_id): c = self.conn.cursor() - c.execute(GET_HISTORY_QUERY, (part_id, )) + c.execute(GET_HISTORY_QUERY, (part_id,)) return c.fetchall() def new_part_history_event(self, part_id, movement, comment): @@ -146,25 +146,25 @@ class PartsDB(): def update_part_qty(self, part_id, movement): with self.conn: c = self.conn.cursor() - c.execute(GET_PART_QTY, (part_id, )) - qty = c.fetchone()['quantity'] + c.execute(GET_PART_QTY, (part_id,)) + qty = c.fetchone()["quantity"] qty = qty + movement c.execute(UPDATE_PART_QTY_QUERY, (qty, part_id)) def delete_part(self, part_id): with self.conn: c = self.conn.cursor() - c.execute(DELETE_HISTORY_QUERY, (part_id, )) - c.execute(DELETE_PART_QUERY, (part_id, )) + c.execute(DELETE_HISTORY_QUERY, (part_id,)) + c.execute(DELETE_PART_QUERY, (part_id,)) def get_image(self, part_id): c = self.conn.cursor() - c.execute(GET_IMAGE_QUERY, (part_id, )) + c.execute(GET_IMAGE_QUERY, (part_id,)) return c.fetchone() def get_datasheet(self, part_id): c = self.conn.cursor() - c.execute(GET_DATASHEET_QUERY, (part_id, )) + c.execute(GET_DATASHEET_QUERY, (part_id,)) return c.fetchone() def close(self): diff --git a/partsdb/helpers.py b/partsdb/helpers.py @@ -8,12 +8,12 @@ import tempfile import time -def print_parts_list(parts, output='full'): - if output == 'short': +def print_parts_list(parts, output="full"): + if output == "short": _list_ascii_short(parts) - if output == 'full': + if output == "full": _list_ascii(parts) - if output == 'json': + if output == "json": _list_json(parts) @@ -22,79 +22,86 @@ def _list_ascii(parts): # passed and comparison performed, so we look at the longest desc to # get the lenght of the field. We also check if the header is longer, # the result is whatever is longer - l_pn = len(max(parts, key=lambda k: len(k['pn']))['pn']) - l_cat = len(max(parts, key=lambda k: len(k['cname']))['cname']) - l_man = len( - max(parts, key=lambda k: len(k['manufacturer']))['manufacturer']) - l_desc = len( - max(parts, key=lambda k: len(k['description']))['description']) - l_cat = l_cat if l_cat > len('Category') else len('Category') - l_man = l_man if l_man > len('Manufacturer') else len('Manufacturer') - l_desc = l_desc if l_desc > len('Description') else len('Description') - - header = (f"| {'ID':5} | {'PN':{l_pn}} | " - f"{'Category':{l_cat}} | " - f"{'Manufacturer':{l_man}} | " - f"{'Description':{l_desc}} | " - f"{'Type':4} | " - f"{'Footp':6} | " - f"{'Qty':4} |") + l_pn = len(max(parts, key=lambda k: len(k["pn"]))["pn"]) + l_cat = len(max(parts, key=lambda k: len(k["cname"]))["cname"]) + l_man = len(max(parts, key=lambda k: len(k["manufacturer"]))["manufacturer"]) + l_desc = len(max(parts, key=lambda k: len(k["description"]))["description"]) + l_cat = l_cat if l_cat > len("Category") else len("Category") + l_man = l_man if l_man > len("Manufacturer") else len("Manufacturer") + l_desc = l_desc if l_desc > len("Description") else len("Description") + + header = ( + f"| {'ID':5} | {'PN':{l_pn}} | " + f"{'Category':{l_cat}} | " + f"{'Manufacturer':{l_man}} | " + f"{'Description':{l_desc}} | " + f"{'Type':4} | " + f"{'Footp':6} | " + f"{'Qty':4} |" + ) for i, p in enumerate(parts): if i % 25 == 0: print("-" * len(header)) print(header) print("-" * len(header)) - print(f"| {p['id']:<5} | {p['pn']:{l_pn}} | " - f"{p['cname']:{l_cat}} | " - f"{_sanitize_value(p['manufacturer']):{l_man}} | " - f"{_sanitize_value(p['description']):{l_desc}} | " - f"{_sanitize_value(p['part_type'])[0:3]:4} | " - f"{_sanitize_value(p['footprint'])[0:5]:6} | " - f"{p['quantity']:4} |") + print( + f"| {p['id']:<5} | {p['pn']:{l_pn}} | " + f"{p['cname']:{l_cat}} | " + f"{_sanitize_value(p['manufacturer']):{l_man}} | " + f"{_sanitize_value(p['description']):{l_desc}} | " + f"{_sanitize_value(p['part_type'])[0:3]:4} | " + f"{_sanitize_value(p['footprint'])[0:5]:6} | " + f"{p['quantity']:4} |" + ) def _list_ascii_short(parts): - header = (f"| {'ID':4} | " - f"{'Category':8} | " - f"{'PN':10} | " - f"{'Manufacturer':16} | " - f"{'Description':25} |") + header = ( + f"| {'ID':4} | " + f"{'Category':8} | " + f"{'PN':10} | " + f"{'Manufacturer':16} | " + f"{'Description':25} |" + ) for i, p in enumerate(parts): if i % 25 == 0: print("-" * 79) print(header) print("-" * 79) - print(f"| {p['id']:<4} | " - f"{p['cname'][0:7]:8} | " - f"{_sanitize_value(p['pn'])[0:9]:10} | " - f"{_sanitize_value(p['manufacturer'])[0:15]:16} | " - f"{_sanitize_value(p['description'])[0:24]:25} |") + print( + f"| {p['id']:<4} | " + f"{p['cname'][0:7]:8} | " + f"{_sanitize_value(p['pn'])[0:9]:10} | " + f"{_sanitize_value(p['manufacturer'])[0:15]:16} | " + f"{_sanitize_value(p['description'])[0:24]:25} |" + ) def _list_json(parts): p = [dict(zip(part.keys(), part)) for part in parts] - print(json.dumps({'parts': p})) + print(json.dumps({"parts": p})) -def print_part(p, history, output='full'): - if output == 'full': +def print_part(p, history, output="full"): + if output == "full": _part_ascii(p, history) - if output == 'json': + if output == "json": _part_json(p, history) def _part_ascii(p, history): print(f"PN: {p['pn']}\tManufacturer: {p['manufacturer']}") - print(f"Category: {p['cat']}\tType: {p['part_type']}" - f"\tFootprint: {p['footprint']}") + print( + f"Category: {p['cat']}\tType: {p['part_type']}" f"\tFootprint: {p['footprint']}" + ) print(f"Storage: {p['storage']}") print(f"Created: {p['insert_date']}" f"\tUpdated: {p['update_date']}\n") print(f"Description:\n{p['description']}\n") print(f"Specs:\n{p['specs']}") - if p['datasheet'] is not None: + if p["datasheet"] is not None: print("This part has a datasheet available.") - if p['image'] is not None: + if p["image"] is not None: print("This part has an image available.") print(f"\nQuantity: {p['quantity']}") @@ -102,20 +109,18 @@ def _part_ascii(p, history): if history: print("History:") for h in history: - print(f"{h['insert_date']} | " - f"{h['movement']:4} | " - f"{h['mcomment']}") + print(f"{h['insert_date']} | " f"{h['movement']:4} | " f"{h['mcomment']}") def _part_json(part, history): part = dict(zip(part.keys(), part)) # remove bytes data if present. For now is useless in this output. - del part['datasheet'] - del part['image'] + del part["datasheet"] + del part["image"] - part['history'] = [dict(zip(h.keys(), h)) for h in history] + part["history"] = [dict(zip(h.keys(), h)) for h in history] - print(json.dumps({'part': part})) + print(json.dumps({"part": part})) def _sanitize_value(value): @@ -127,38 +132,38 @@ def _sanitize_value(value): def open_file(content, extension): with tempfile.NamedTemporaryFile(suffix=extension) as f: f.write(content) - subprocess.Popen(['xdg-open', f.name], start_new_session=True) + subprocess.Popen(["xdg-open", f.name], start_new_session=True) time.sleep(2) def html_main_index(dest_folder, categories, env): - tpl = env.get_template('index.html') - with open(f'{dest_folder}/index.html', 'w') as f: + tpl = env.get_template("index.html") + with open(f"{dest_folder}/index.html", "w") as f: f.write(tpl.render(cat=categories)) def html_category_index(dest_folder, category, parts, env): - tpl = env.get_template('cat.html') - with open(f"{dest_folder}/cat_list_{category['id']}.html", 'w') as f: + tpl = env.get_template("cat.html") + with open(f"{dest_folder}/cat_list_{category['id']}.html", "w") as f: f.write(tpl.render(category=category, parts=parts)) def html_part(dest_folder, part, part_history, env): - tpl = env.get_template('part.html') - with open(f"{dest_folder}/part_{part['id']}.html", 'w') as f: + tpl = env.get_template("part.html") + with open(f"{dest_folder}/part_{part['id']}.html", "w") as f: f.write(tpl.render(part=part, history=part_history)) def html_css(dest_folder, env): - tpl = env.get_template('style.css') - with open(f"{dest_folder}/style.css", 'w') as f: + tpl = env.get_template("style.css") + with open(f"{dest_folder}/style.css", "w") as f: f.write(tpl.render()) def html_attachments(dest_folder, part_id, datasheet, image): - if datasheet['datasheet'] is not None: - with open(f"{dest_folder}/part_datasheet_{part_id}.pdf", 'wb') as f: - f.write(datasheet['datasheet']) - if image['image'] is not None: - with open(f"{dest_folder}/part_image_{part_id}.jpg", 'wb') as f: - f.write(image['image']) + if datasheet["datasheet"] is not None: + with open(f"{dest_folder}/part_datasheet_{part_id}.pdf", "wb") as f: + f.write(datasheet["datasheet"]) + if image["image"] is not None: + with open(f"{dest_folder}/part_image_{part_id}.jpg", "wb") as f: + f.write(image["image"]) diff --git a/partsdb/octopart.py b/partsdb/octopart.py @@ -7,7 +7,7 @@ import sys import urllib.error import urllib.request -GET_PARTS_GRAPHQL_QUERY = ''' +GET_PARTS_GRAPHQL_QUERY = """ query MyPartSearch($q: String!, $filters: Map) { search(q: $q, filters: $filters, limit: 5) { total @@ -52,15 +52,15 @@ GET_PARTS_GRAPHQL_QUERY = ''' } } } -''' +""" # copy pasted from: https://github.com/prisma-labs/python-graphql-client/blob/master/graphqlclient/client.py -class OctopartClient(): +class OctopartClient: def __init__(self, token): - self.endpoint = 'https://octopart.com/api/v4/endpoint' + self.endpoint = "https://octopart.com/api/v4/endpoint" if token is None: - print('I need OCTOPART_TOKEN') + print("I need OCTOPART_TOKEN") sys.exit(1) self.token = token @@ -68,21 +68,19 @@ class OctopartClient(): return self._send(query, variables) def _send(self, query, variables): - data = {'query': query, 'variables': variables} - headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } + data = {"query": query, "variables": variables} + headers = {"Accept": "application/json", "Content-Type": "application/json"} if self.token is not None: - headers['token'] = '{}'.format(self.token) + headers["token"] = "{}".format(self.token) - req = urllib.request.Request(self.endpoint, - json.dumps(data).encode('utf-8'), headers) + req = urllib.request.Request( + self.endpoint, json.dumps(data).encode("utf-8"), headers + ) try: response = urllib.request.urlopen(req) - return response.read().decode('utf-8') + return response.read().decode("utf-8") except urllib.error.HTTPError as e: print((e.read())) raise e diff --git a/partsdb/partsdb.py b/partsdb/partsdb.py @@ -13,41 +13,40 @@ from partsdb import database as pdb from partsdb import helpers from partsdb import octopart as oc -__version__ = 'v1.3.0' -octo = oc.OctopartClient(os.getenv('OCTOPART_TOKEN', None)) +__version__ = "v1.3.0" +octo = oc.OctopartClient(os.getenv("OCTOPART_TOKEN", None)) db = pdb.PartsDB( - os.getenv('PARTSDB_FILE', - f"{os.getenv('HOME')}/.local/share/partsdb/parts.db")) + os.getenv("PARTSDB_FILE", f"{os.getenv('HOME')}/.local/share/partsdb/parts.db") +) def add_part(mpn, quantity, category, storage, part_type): - result = octo.get_part(mpn)['data']['search']['results'] + result = octo.get_part(mpn)["data"]["search"]["results"] if result is None: print(f"Can't find results for {sys.argv[1]} on Octopart") sys.exit(0) # list results from Octopart and pick one for i, r in enumerate(result): - print('-' * 79) - print(f"{i}\t{r['part']['manufacturer']['name']}" - f"\t{r['part']['mpn']}") + print("-" * 79) + print(f"{i}\t{r['part']['manufacturer']['name']}" f"\t{r['part']['mpn']}") print(f"\t{r['part']['short_description']}") print("Sold by:") - for s in r['part']['sellers']: - print(s['company']['name']) + for s in r["part"]["sellers"]: + print(s["company"]["name"]) print(f"\t{r['part']['octopart_url']}") pick = int(input("Which one seems better ? ")) - p = result[pick]['part'] + p = result[pick]["part"] if quantity is None: quantity = int(input("How many of them ? ")) # if this exists we increment stock - spart = db.get_part_by_mpn(p['mpn']) + spart = db.get_part_by_mpn(p["mpn"]) if spart: - db.update_part_qty(spart['id'], quantity) - db.new_part_history_event(spart['id'], quantity, "new buy") + db.update_part_qty(spart["id"], quantity) + db.new_part_history_event(spart["id"], quantity, "new buy") return # only ask for categories if we do not have one already @@ -65,57 +64,67 @@ def add_part(mpn, quantity, category, storage, part_type): storage = int(input("Where will you store it ? ")) # only ask for part type if we do not have one already - if part_type == 'none': + if part_type == "none": smd = input("Is this an SMD part (y/n, default yes) ? ") - if smd == 'n' or smd == 'N': - part_type = 'th' + if smd == "n" or smd == "N": + part_type = "th" else: - part_type = 'smd' + part_type = "smd" footprint = None datasheet = None image = None - specs = '' + specs = "" - if 'specs' in p: - for s in p['specs']: + if "specs" in p: + for s in p["specs"]: specs += f"{s['attribute']['name']}: {s['display_value']}\n" - if s['attribute']['shortname'] == 'case_package': - footprint = s['display_value'] + if s["attribute"]["shortname"] == "case_package": + footprint = s["display_value"] if not specs: specs = None - if 'best_datasheet' in p: - if p['best_datasheet']['mime_type'] == 'application/pdf': - datasheet = p['best_datasheet']['url'] - elif 'document_collections' in p: - for d in p['document_collections'][0]['documents']: - if (d['mime_type'] == 'application/pdf' - and d['name'] == 'Datasheet'): - datasheet = d['url'] + if "best_datasheet" in p: + if p["best_datasheet"]["mime_type"] == "application/pdf": + datasheet = p["best_datasheet"]["url"] + elif "document_collections" in p: + for d in p["document_collections"][0]["documents"]: + if d["mime_type"] == "application/pdf" and d["name"] == "Datasheet": + datasheet = d["url"] headers = {} - headers['User-Agent'] = "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:48.0) Gecko/20100101 Firefox/48.0" + headers[ + "User-Agent" + ] = "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:48.0) Gecko/20100101 Firefox/48.0" if datasheet is not None: req = urllib.request.Request(datasheet, headers=headers) datasheet = urllib.request.urlopen(req).read() - if 'best_image' in p: - image = p['best_image']['url'] + if "best_image" in p: + image = p["best_image"]["url"] if image is not None: req = urllib.request.Request(image, headers=headers) image = urllib.request.urlopen(req).read() part = [ - p['mpn'], p['mpn'], p['manufacturer']['name'], p['short_description'], - specs, footprint, category, storage, quantity, datasheet, image, - part_type + p["mpn"], + p["mpn"], + p["manufacturer"]["name"], + p["short_description"], + specs, + footprint, + category, + storage, + quantity, + datasheet, + image, + part_type, ] new_id = db.new_part(part) db.new_part_history_event(new_id, quantity, "first purchase") def list_parts(category, short): - if category == 'all': + if category == "all": parts = db.list_parts() else: parts = db.list_parts_by_category(category) @@ -145,18 +154,18 @@ def get_part(part_id, output): def open_image(part_id): image = db.get_image(part_id) - if not image['image']: + if not image["image"]: print(f"There's no image for this part ID ({part_id})") return - helpers.open_file(image['image'], '.jpg') + helpers.open_file(image["image"], ".jpg") def open_datasheet(part_id): datasheet = db.get_datasheet(part_id) - if not datasheet['datasheet']: + if not datasheet["datasheet"]: print(f"There's no datasheet for this part ID ({part_id})") return - helpers.open_file(datasheet['datasheet'], '.pdf') + helpers.open_file(datasheet["datasheet"], ".pdf") def delete_part(part_id): @@ -169,21 +178,23 @@ def adjust_stock(part_id, stock_mod, comment): def export_db(dest_folder): - env = Environment(loader=PackageLoader('partsdb.exports', 'templates'), - autoescape=select_autoescape(['html', 'xml'])) + env = Environment( + loader=PackageLoader("partsdb.exports", "templates"), + autoescape=select_autoescape(["html", "xml"]), + ) categories = db.get_categories() helpers.html_main_index(dest_folder, categories, env) for c in categories: - parts = db.list_parts_by_category(c['name']) + parts = db.list_parts_by_category(c["name"]) helpers.html_category_index(dest_folder, c, parts, env) parts = db.list_parts() for p in parts: - part = db.get_part(p['id']) - history = db.get_part_history(p['id']) + part = db.get_part(p["id"]) + history = db.get_part_history(p["id"]) helpers.html_part(dest_folder, part, history, env) - image = db.get_image(p['id']) - datasheet = db.get_datasheet(p['id']) - helpers.html_attachments(dest_folder, p['id'], datasheet, image) + image = db.get_image(p["id"]) + datasheet = db.get_datasheet(p["id"]) + helpers.html_attachments(dest_folder, p["id"], datasheet, image) helpers.html_css(dest_folder, env) @@ -197,10 +208,9 @@ def list_categories(): def main(): ap = argparse.ArgumentParser() - ap.add_argument('--version', - '-v', - action='version', - version='%(prog)s ' + __version__) + ap.add_argument( + "--version", "-v", action="version", version="%(prog)s " + __version__ + ) # Place for global options here # parser.add_argument(...) # And then the commands @@ -208,67 +218,65 @@ def main(): # add ap_add = asp.add_parser("add", help="Add new part from Octopart") ap_add.add_argument("mpn", help="Manufacturer part number") - ap_add.add_argument("-q", - dest='quantity', - help="Quantity of new items", - type=int) - ap_add.add_argument("-c", - dest='category', - help="Which categoryId it belongs to", - type=int) - ap_add.add_argument("-s", - dest='storage', - help="Which storageId it belongs to", - type=int) - ap_add.add_argument("-t", - dest='type', - choices=['smd', 'th'], - default='none', - help="Trhough-hole of smd ?") + ap_add.add_argument("-q", dest="quantity", help="Quantity of new items", type=int) + ap_add.add_argument( + "-c", dest="category", help="Which categoryId it belongs to", type=int + ) + ap_add.add_argument( + "-s", dest="storage", help="Which storageId it belongs to", type=int + ) + ap_add.add_argument( + "-t", + dest="type", + choices=["smd", "th"], + default="none", + help="Trhough-hole of smd ?", + ) # cat asp.add_parser("cat", help="List categories") # list - ap_list = asp.add_parser("list", - help="List all parts from a category (or all)") + ap_list = asp.add_parser("list", help="List all parts from a category (or all)") ap_list.add_argument("category", help="Category Name or ID") - ap_list.add_argument("-o", - dest='output', - choices=['full', 'short', 'json'], - default='full', - help="Short output") + ap_list.add_argument( + "-o", + dest="output", + choices=["full", "short", "json"], + default="full", + help="Short output", + ) # search ap_search = asp.add_parser("search", help="Search for parts") ap_search.add_argument("search_term", help="Term to search for") - ap_search.add_argument("-o", - dest='output', - choices=['full', 'short', 'json'], - default='full', - help="Short output") + ap_search.add_argument( + "-o", + dest="output", + choices=["full", "short", "json"], + default="full", + help="Short output", + ) # get ap_get = asp.add_parser("get", help="Get all details for a part") ap_get.add_argument("part_id", help="Part Id", type=int) - ap_get.add_argument("-d", - dest='datasheet', - action='store_true', - help="Open datasheet if available.") - ap_get.add_argument("-i", - dest='image', - action='store_true', - help="Open image if available.") - ap_get.add_argument("-o", - dest='output', - choices=['full', 'json'], - default='full', - help="Short output") + ap_get.add_argument( + "-d", dest="datasheet", action="store_true", help="Open datasheet if available." + ) + ap_get.add_argument( + "-i", dest="image", action="store_true", help="Open image if available." + ) + ap_get.add_argument( + "-o", + dest="output", + choices=["full", "json"], + default="full", + help="Short output", + ) # delete ap_delete = asp.add_parser("delete", help="Delete a part") ap_delete.add_argument("part_id", help="Part Id", type=int) # stock ap_stock = asp.add_parser("stock", help="Modifies a part stock") ap_stock.add_argument("part_id", help="Part Id", type=int) - ap_stock.add_argument("stock_mod", - help="Stock modifier (+ or -) int", - type=int) + ap_stock.add_argument("stock_mod", help="Stock modifier (+ or -) int", type=int) ap_stock.add_argument("comment", help="Reason for the stock mod") # export ap_export = asp.add_parser("export", help="Exports DB to HTML") @@ -279,26 +287,25 @@ def main(): ap.print_help() sys.exit(0) - if args.command == 'add': - add_part(args.mpn, args.quantity, args.category, args.storage, - args.type) - elif args.command == 'list': + if args.command == "add": + add_part(args.mpn, args.quantity, args.category, args.storage, args.type) + elif args.command == "list": list_parts(args.category, args.output) - elif args.command == 'search': + elif args.command == "search": search_part(args.search_term, args.output) - elif args.command == 'get': + elif args.command == "get": get_part(args.part_id, args.output) - if args.datasheet and args.output != 'json': + if args.datasheet and args.output != "json": open_datasheet(args.part_id) - if args.image and args.output != 'json': + if args.image and args.output != "json": open_image(args.part_id) - elif args.command == 'delete': + elif args.command == "delete": delete_part(args.part_id) - elif args.command == 'stock': + elif args.command == "stock": adjust_stock(args.part_id, args.stock_mod, args.comment) - elif args.command == 'export': + elif args.command == "export": export_db(args.dest_folder) - elif args.command == 'cat': + elif args.command == "cat": list_categories() else: ap.print_help() @@ -307,5 +314,5 @@ def main(): db.close() -if __name__ == '__main__': +if __name__ == "__main__": main()