partsdb.py (9368B)
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 # vim:fenc=utf-8 4 5 import argparse 6 import os 7 import sys 8 import urllib.request 9 10 from jinja2 import Environment, PackageLoader, select_autoescape 11 12 from partsdb import database as pdb 13 from partsdb import helpers 14 from partsdb import octopart as oc 15 16 __version__ = 'v1.1.0' 17 octo = oc.OctopartClient(os.getenv('OCTOPART_TOKEN', None)) 18 db = pdb.PartsDB(os.getenv('PARTSDB_FILE', 19 f"{os.getenv('HOME')}/.local/share/parts.db")) 20 21 22 def add_part(mpn, quantity, category, storage, part_type): 23 result = octo.get_part(mpn)['data']['search']['results'] 24 if result is None: 25 print(f"Can't find results for {sys.argv[1]} on Octopart") 26 sys.exit(0) 27 28 # list results from Octopart and pick one 29 for i, r in enumerate(result): 30 print('-'*79) 31 print(f"{i}\t{r['part']['manufacturer']['name']}" 32 f"\t{r['part']['mpn']}") 33 print(f"\t{r['part']['short_description']}") 34 35 pick = int(input("Which one seems better ? ")) 36 p = result[pick]['part'] 37 38 if quantity is None: 39 quantity = int(input("How many of them ? ")) 40 41 # if this exists we increment stock 42 spart = db.get_part_by_mpn(p['mpn']) 43 if spart: 44 db.update_part_qty(spart['id'], quantity) 45 db.new_part_history_event(spart['id'], quantity, "new buy") 46 return 47 48 # only ask for categories if we do not have one already 49 # list categories to choose from 50 if category is None: 51 for c in db.get_categories(): 52 print(f"{c['id']}) {c['name']}") 53 category = int(input("In which category do you want it in ? ")) 54 55 # only ask for storage if we do not have one already 56 # list storages to choose from 57 if storage is None: 58 for s in db.get_storages(): 59 print(f"{s['id']}) {s['name']}") 60 storage = int(input("Where will you store it ? ")) 61 62 # only ask for part type if we do not have one already 63 if part_type == 'none': 64 smd = input("Is this an SMD part (y/n, default yes) ? ") 65 if smd == 'n' or smd == 'N': 66 part_type = 'th' 67 else: 68 part_type = 'smd' 69 70 footprint = None 71 datasheet = None 72 image = None 73 specs = '' 74 75 if 'specs' in p: 76 for s in p['specs']: 77 specs += f"{s['attribute']['name']}: {s['display_value']}\n" 78 if s['attribute']['shortname'] == 'case_package': 79 footprint = s['display_value'] 80 if not specs: 81 specs = None 82 83 if 'best_datasheet' in p: 84 if p['best_datasheet']['mime_type'] == 'application/pdf': 85 datasheet = p['best_datasheet']['url'] 86 elif 'document_collections' in p: 87 for d in p['document_collections'][0]['documents']: 88 if d['mime_type'] == 'application/pdf' and d['name'] == 'Datasheet': 89 datasheet = d['url'] 90 headers = {} 91 headers['User-Agent'] = "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:48.0) Gecko/20100101 Firefox/48.0" 92 if datasheet is not None: 93 req = urllib.request.Request(datasheet, headers=headers) 94 datasheet = urllib.request.urlopen(req).read() 95 96 if 'best_image' in p: 97 image = p['best_image']['url'] 98 if image is not None: 99 req = urllib.request.Request(image, headers=headers) 100 image = urllib.request.urlopen(req).read() 101 102 part = [ 103 p['mpn'], 104 p['mpn'], 105 p['manufacturer']['name'], 106 p['short_description'], 107 specs, 108 footprint, 109 category, 110 storage, 111 quantity, 112 datasheet, 113 image, 114 part_type 115 ] 116 new_id = db.new_part(part) 117 db.new_part_history_event(new_id, quantity, "first purchase") 118 119 120 def list_parts(category, short): 121 if category == 'all': 122 parts = db.list_parts() 123 else: 124 parts = db.list_parts_by_category(category) 125 126 if not parts: 127 print("There are no parts in this category") 128 return 129 130 helpers.print_parts_list(parts, short) 131 132 133 def search_part(search_term): 134 parts = db.search_parts(search_term) 135 136 if not parts: 137 print("No parts found") 138 return 139 140 helpers.print_parts_list(parts) 141 142 143 def get_part(part_id): 144 part = db.get_part(part_id) 145 history = db.get_part_history(part_id) 146 helpers.print_part(part, history) 147 148 149 def open_image(part_id): 150 image = db.get_image(part_id) 151 if image is None: 152 print(f"There's no image for this part ID ({part_id})") 153 return 154 helpers.open_file(image['image'], '.jpg') 155 156 157 def open_datasheet(part_id): 158 datasheet = db.get_datasheet(part_id) 159 if datasheet is None: 160 print(f"There's no datasheet for this part ID ({part_id})") 161 return 162 helpers.open_file(datasheet['datasheet'], '.pdf') 163 164 165 def delete_part(part_id): 166 db.delete_part(part_id) 167 168 169 def adjust_stock(part_id, stock_mod, comment): 170 db.new_part_history_event(part_id, stock_mod, comment) 171 db.update_part_qty(part_id, stock_mod) 172 173 174 def export_db(dest_folder): 175 env = Environment( 176 loader=PackageLoader('partsdb.exports', 'templates'), 177 autoescape=select_autoescape(['html', 'xml']) 178 ) 179 categories = db.get_categories() 180 helpers.html_main_index(dest_folder, categories, env) 181 for c in categories: 182 parts = db.list_parts_by_category(c['name']) 183 helpers.html_category_index(dest_folder, c, parts, env) 184 parts = db.list_parts() 185 for p in parts: 186 part = db.get_part(p['id']) 187 history = db.get_part_history(p['id']) 188 helpers.html_part(dest_folder, part, history, env) 189 image = db.get_image(p['id']) 190 datasheet = db.get_datasheet(p['id']) 191 helpers.html_attachments(dest_folder, p['id'], datasheet, image) 192 helpers.html_css(dest_folder, env) 193 194 195 def list_categories(): 196 categories = db.get_categories() 197 print("ID\tName") 198 print("-"*40) 199 for c in categories: 200 print(f"{c['id']}\t{c['name']}") 201 202 203 def main(): 204 ap = argparse.ArgumentParser() 205 ap.add_argument('--version', '-v', action='version', 206 version='%(prog)s '+__version__) 207 # Place for global options here 208 # parser.add_argument(...) 209 # And then the commands 210 asp = ap.add_subparsers(dest="command") 211 # add 212 ap_add = asp.add_parser("add", help="Add new part from Octopart") 213 ap_add.add_argument("mpn", help="Manufacturer part number") 214 ap_add.add_argument("-q", dest='quantity', 215 help="Quantity of new items", type=int) 216 ap_add.add_argument("-c", dest='category', 217 help="Which categoryId it belongs to", type=int) 218 ap_add.add_argument("-s", dest='storage', 219 help="Which storageId it belongs to", type=int) 220 ap_add.add_argument("-t", dest='type', choices=['smd', 'th'], 221 default='none', help="Trhough-hole of smd ?") 222 # cat 223 ap_cat = asp.add_parser("cat", help="List categories") 224 # list 225 ap_list = asp.add_parser("list", 226 help="List all parts from a category (or all)") 227 ap_list.add_argument("category", help="Category Name") 228 ap_list.add_argument("-s", dest='short', 229 action='store_true', help="Short output") 230 # search 231 ap_search = asp.add_parser("search", help="Search for parts") 232 ap_search.add_argument("search_term", help="Term to search for") 233 # get 234 ap_get = asp.add_parser("get", help="Get all details for a part") 235 ap_get.add_argument("part_id", help="Part Id", type=int) 236 ap_get.add_argument("-d", dest='datasheet', action='store_true', 237 help="Open datasheet if available.") 238 ap_get.add_argument("-i", dest='image', action='store_true', 239 help="Open image if available.") 240 # delete 241 ap_delete = asp.add_parser("delete", help="Delete a part") 242 ap_delete.add_argument("part_id", help="Part Id", type=int) 243 # stock 244 ap_stock = asp.add_parser("stock", help="Modifies a part stock") 245 ap_stock.add_argument("part_id", help="Part Id", type=int) 246 ap_stock.add_argument("stock_mod", 247 help="Stock modifier (+ or -) int", type=int) 248 ap_stock.add_argument("comment", help="Reason for the stock mod") 249 # export 250 ap_export = asp.add_parser("export", help="Exports DB to HTML") 251 ap_export.add_argument("dest_folder", help="Destination folder") 252 253 args = ap.parse_args() 254 if not args.command: 255 ap.print_help() 256 sys.exit(0) 257 258 if args.command == 'add': 259 add_part(args.mpn, args.quantity, args.category, args.storage, 260 args.type) 261 elif args.command == 'list': 262 list_parts(args.category, args.short) 263 elif args.command == 'search': 264 search_part(args.search_term) 265 elif args.command == 'get': 266 get_part(args.part_id) 267 if args.datasheet: 268 open_datasheet(args.part_id) 269 if args.image: 270 open_image(args.part_id) 271 elif args.command == 'delete': 272 delete_part(args.part_id) 273 elif args.command == 'stock': 274 adjust_stock(args.part_id, args.stock_mod, args.comment) 275 elif args.command == 'export': 276 export_db(args.dest_folder) 277 elif args.command == 'cat': 278 list_categories() 279 else: 280 ap.print_help() 281 sys.exit(0) 282 283 db.close() 284 285 286 if __name__ == '__main__': 287 main()