from pathlib import Path from typing import List from ee.db import ObjDb from ee.digikey import Digikey, DigikeyParser, DigikeyClient, SearchResponseTypes, DigikeyProduct, DigikeyStore from ee.part import PartDb, load_db, save_db, Part from ee.xml import types from ee.xml.uris import make_digikey_fact_key __all__ = ["search_parts"] def resolved(supplier, p: DigikeyProduct) -> Part: # TODO: fix uri xml = types.Part(uri="https://digikey.com/pn#{}".format(p.part_number), supplier=supplier, description=p.description, links=types.LinkList(), facts=types.FactList(), references=types.ReferenceList()) part = Part(xml) if p.url: part.get_links().append(types.Link(url=p.url, relation="canonical", media_type="text/html")) for d in p.documents: title = "{}: {}".format(d.kind, d.title) part.get_links().append(types.Link(url=d.url, relation="http://purl.org/ee/link-relation#documentation", media_type="text/html", title=title)) part.add_spn(p.part_number) if p.mpn: part.add_mpn(p.mpn) facts: List[types.Fact] = xml.factsProp.factProp for a in p.attributes: key = make_digikey_fact_key(a.attribute_type.id) facts.append(types.Fact(key=key, label=a.attribute_type.label, value=a.value)) if len(p.price_breaks): xml.price_breaksProp = types.PriceBreakList() price_breaks: List[types.PriceBreak] = xml.price_breaksProp.price_break for pb in p.price_breaks: amount = types.Amount(value=str(pb.per_piece_price.amount), currency=pb.per_piece_price.currency) price_breaks.append(types.PriceBreak(pb.quantity, amount=amount)) return part def search_parts(in_path: Path, out_path: Path, cache_dir: Path, store_code, print_result=True): in_db = load_db(in_path) store = DigikeyStore.from_store_code(store_code) parser = DigikeyParser(Digikey()) client = DigikeyClient(store.products_url, cache_dir) out_parts: ObjDb[Part] = ObjDb[Part]() uri_idx = out_parts.add_unique_index("uri", lambda p: p.uri) pn_idx = out_parts.add_index("pn", lambda p: [pn.value for pn in p.get_part_references()], multiple=True) spn_idx = out_parts.add_index("spn", lambda p: [pn.value for pn in p.get_spns()], multiple=True) for xml in in_db.iterparts(): if xml.supplierProp is not None and xml.supplierProp != store.url: continue part = Part(xml) dpn = part.get_only_spn() mpn = part.get_only_mpn() is_mpn = query = None if dpn is not None: query = dpn.valueProp is_mpn = False elif mpn is not None: query = mpn.valueProp is_mpn = True if query is None: # TODO: use schematic reference print("could not find pn or dpn: part.uri={}".format(xml.uriProp)) continue out_part = None result = None text = client.search(query) response = parser.parse_string(text) if response.response_type == SearchResponseTypes.SINGLE: out_part = resolved(store.url, response.products[0]) elif response.response_type == SearchResponseTypes.MANY: # find those with an exact match. Digikey uses a prefix search so a query for "FOO" will return "FOO" # and "FOOT". def get_field(p): return p.mpn if is_mpn else p.part_number filtered_products = [p for p in response.products if get_field(p) == query] if len(filtered_products) == 0: result = "not-found" else: dpn = sorted(filtered_products, key=lambda p: p.part_number)[0].part_number response = parser.parse_string(client.search(dpn)) if response.response_type == SearchResponseTypes.SINGLE: out_part = resolved(store.url, response.products[0]) else: result = "many" elif response.response_type == SearchResponseTypes.TOO_MANY: result = "too-many" elif response.response_type == SearchResponseTypes.NO_MATCHES: result = "not-found" if out_part: if out_part.uri not in uri_idx: out_parts.add(out_part) if print_result and result: print("{}: {}".format(query, result)) part_db = PartDb() for part in out_parts: part_db.add_entry(part, True) save_db(out_path, part_db, sort=True)