main
noah amfm-receiver-molecule: KiCad project, fab files, 3D and README (6/7) 53707c4 1d ago
"""JLCPCB parts lookup (public parts-search API) with a small on-disk cache."""
import json, os, time, urllib.request, hashlib

API = 'https://jlcpcb.com/api/overseas-pcb-order/v1/shoppingCart/smtGood/selectSmtComponentList'
CACHE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build', 'jlc_cache')


def _post(body):
    os.makedirs(CACHE, exist_ok=True)
    key = hashlib.sha1(json.dumps(body, sort_keys=True).encode()).hexdigest()
    path = os.path.join(CACHE, key + '.json')
    if os.path.exists(path) and time.time() - os.path.getmtime(path) < 6 * 3600:
        return json.load(open(path))
    req = urllib.request.Request(API, data=json.dumps(body).encode(), method='POST',
                                 headers={'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0'})
    for attempt in range(4):
        try:
            with urllib.request.urlopen(req, timeout=40) as r:
                d = json.load(r)
            json.dump(d, open(path, 'w'))
            return d
        except Exception as e:
            time.sleep(2 + 3 * attempt)
            err = e
    raise err


def norm(it):
    attrs = {a.get('attribute_name_en'): a.get('attribute_value_name') for a in (it.get('attributes') or [])}
    return dict(code=it['componentCode'], mpn=it.get('componentModelEn'), brand=it.get('componentBrandEn'),
                package=it.get('componentSpecificationEn'), type=it.get('componentLibraryType'),
                preferred=bool(it.get('preferredComponentFlag')), stock=it.get('stockCount') or 0,
                category=it.get('firstSortName'), describe=it.get('describe') or '', attrs=attrs,
                datasheet=it.get('dataManualUrl'), price=(it.get('componentPrices') or [{}])[0].get('productPrice'))


def search(keyword, size=50, pages=1):
    out = []
    for pg in range(1, pages + 1):
        d = _post({'keyword': keyword, 'currentPage': pg, 'pageSize': size})
        lst = ((d.get('data') or {}).get('componentPageInfo') or {}).get('list') or []
        out += [norm(i) for i in lst]
        if len(lst) < size:
            break
    return out


def part(code):
    r = [p for p in search(code, size=10) if p['code'] == code]
    return r[0] if r else None


def fmt(p):
    return '%-10s %-6s%s stock=%-9d %-24s %-18s %s' % (
        p['code'], p['type'], '*' if p['preferred'] else ' ', p['stock'], (p['mpn'] or '')[:24],
        (p['package'] or '')[:18], p['describe'][:90])


if __name__ == '__main__':
    import sys
    for q in sys.argv[1:]:
        print('==', q)
        for p in search(q, size=15):
            print('  ', fmt(p))