From 157b59d3ed516c67ba08cb0dcb3259d8f227bb4b Mon Sep 17 00:00:00 2001 From: T0jan Date: Wed, 16 Oct 2024 15:36:09 +0200 Subject: [PATCH 01/18] update requirements.txt: * add setuptools tool prevent incompabilities due to distutils not being available in python3.12 * replace digikey API with fork * bump flet to latest testet version 0.24.1 * bump multiple packages to latest available version --- requirements.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1ce755e4..eda2c73d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ cloudscraper==1.2.71 -digikey-api>=1.0.0,<2.0 -Flet>=0.22.0,<0.23.0 +setuptools==75.2.0 +https://github.com/hurricaneJoef/digikey-api/archive/refs/heads/master.zip +Flet>=0.24.1,<1.0 thefuzz>=0.19.0,<1.0 -inventree>=0.13.3,<1.0 +inventree>=0.17.1,<1.0 kiutils>=1.4.8,<2.0 -mouser>=0.1.3,<1.0 +mouser>=0.1.6,<1.0 multiprocess>=0.70.16,<0.71 PyYAML>=6.0.1,<7.0 validators>=0.19.0,<1.0 From 1425b59fccc0bcb6b4ae56a6b419d30f83f1fd37 Mon Sep 17 00:00:00 2001 From: T0jan Date: Wed, 16 Oct 2024 15:45:26 +0200 Subject: [PATCH 02/18] make LCSC data processing more robust solves https://github.com/sparkmicro/Ki-nTree/issues/261 --- kintree/search/lcsc_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kintree/search/lcsc_api.py b/kintree/search/lcsc_api.py index ef37dc12..bc3f6ab0 100644 --- a/kintree/search/lcsc_api.py +++ b/kintree/search/lcsc_api.py @@ -63,14 +63,14 @@ def search_timeout(timeout=10): try: part = search_timeout() except: - part = None + part = {} - if not part: - return part_info - # Extract result part = part.get('result', None) + if not part: + return part_info + category, subcategory = find_categories(part) try: part_info['category'] = category From 4b5f750968f3a2bad943a1f50a5b691205f710b8 Mon Sep 17 00:00:00 2001 From: T0jan Date: Thu, 17 Oct 2024 13:13:16 +0200 Subject: [PATCH 03/18] change digikey_api.py to use digikey API V4 syntax --- kintree/search/digikey_api.py | 73 +++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py index 13cf3a33..043a8515 100644 --- a/kintree/search/digikey_api.py +++ b/kintree/search/digikey_api.py @@ -5,26 +5,26 @@ from ..config import settings, config_interface SEARCH_HEADERS = [ - 'product_description', - 'detailed_description', + 'description', 'digi_key_part_number', 'manufacturer', - 'manufacturer_part_number', + 'manufacturer_product_number', 'product_url', - 'primary_datasheet', - 'primary_photo', + 'datasheet_url', + 'photo_url', ] PARAMETERS_MAP = [ 'parameters', - 'parameter', - 'value', + 'parameter_text', + 'value_text', ] PRICING_MAP = [ + 'product_variations', + 'digi_key_product_number', 'standard_pricing', 'break_quantity', 'unit_price', - 'currency', ] @@ -69,10 +69,10 @@ def get_default_search_keys(): 'keywords', 'digi_key_part_number', 'manufacturer', - 'manufacturer_part_number', + 'manufacturer_product_number', 'product_url', - 'primary_datasheet', - 'primary_photo', + 'datasheet_url', + 'photo_url', ] @@ -130,6 +130,11 @@ def digikey_search_timeout(): if not part: return part_info + if 'product' not in part or not part['product']: + return part_info + + part_info['currency'] = part['search_locale_used']['currency'] + part = part['product'] category, subcategory = find_categories(part) try: @@ -144,7 +149,10 @@ def digikey_search_timeout(): for key in part: if key in headers: if key == 'manufacturer': - part_info[key] = part['manufacturer']['value'] + part_info[key] = part['manufacturer'].get('name') + elif key == 'description': + part_info['product_description'] = part['description'].get('product_description') + part_info['detailed_description'] = part['description'].get('detailed_description') else: part_info[key] = part[key] @@ -152,36 +160,43 @@ def digikey_search_timeout(): part_info['parameters'] = {} [parameter_key, name_key, value_key] = PARAMETERS_MAP - for parameter in range(len(part[parameter_key])): - parameter_name = part[parameter_key][parameter][name_key] - parameter_value = part[parameter_key][parameter][value_key] + for parameter in part[parameter_key]: + parameter_name = parameter.get(name_key, '') + parameter_value = parameter.get(value_key, '') # Append to parameters dictionary part_info['parameters'][parameter_name] = parameter_value - # process export controll class number as an parameter - eccn = part['export_control_class_number'] - if eccn: - part_info['parameters']['ECCN'] = eccn + # process classifications as parameters + for classification, value in part.get('classifications', {}).items(): + part_info['parameters'][classification] = value # Pricing part_info['pricing'] = {} - [pricing_key, qty_key, price_key, currency_key] = PRICING_MAP - - for price_break in part[pricing_key]: - quantity = price_break[qty_key] - price = price_break[price_key] - part_info['pricing'][quantity] = price + [variations_key, + digi_number_key, + pricing_key, + qty_key, + price_key] = PRICING_MAP + + for variation in part[variations_key]: + digi_number = variation.get(digi_number_key) + if not variation.get('digi_reel_fee'): + part_info['digi_key_part_number'] = digi_number + part_info['pricing'][digi_number] = {} + for price_break in variation[pricing_key]: + quantity = price_break[qty_key] + price = price_break[price_key] + part_info['pricing'][digi_number][quantity] = price - part_info['currency'] = part['search_locale_used'][currency_key] # Extra search fields - if settings.CONFIG_DIGIKEY.get('EXTRA_FIELDS', None): + if settings.CONFIG_DIGIKEY.get('EXTRA_FIELDS'): for extra_field in settings.CONFIG_DIGIKEY['EXTRA_FIELDS']: - if part.get(extra_field, None): + if part.get(extra_field): part_info['parameters'][extra_field] = part[extra_field] else: from ..common.tools import cprint cprint(f'[INFO]\tWarning: Extra field "{extra_field}" not found in search results', silent=False) - + print(part_info) return part_info From 979e02779be5ee979a312905858f28f53186e9cf Mon Sep 17 00:00:00 2001 From: T0jan Date: Thu, 17 Oct 2024 13:16:22 +0200 Subject: [PATCH 04/18] remove debug print --- kintree/search/digikey_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py index 043a8515..4c64a27d 100644 --- a/kintree/search/digikey_api.py +++ b/kintree/search/digikey_api.py @@ -187,7 +187,6 @@ def digikey_search_timeout(): price = price_break[price_key] part_info['pricing'][digi_number][quantity] = price - # Extra search fields if settings.CONFIG_DIGIKEY.get('EXTRA_FIELDS'): for extra_field in settings.CONFIG_DIGIKEY['EXTRA_FIELDS']: @@ -196,7 +195,7 @@ def digikey_search_timeout(): else: from ..common.tools import cprint cprint(f'[INFO]\tWarning: Extra field "{extra_field}" not found in search results', silent=False) - print(part_info) + return part_info From fa979e140fbd21ea8978042bfc3ee83ea67f5c84 Mon Sep 17 00:00:00 2001 From: T0jan Date: Thu, 17 Oct 2024 13:43:21 +0200 Subject: [PATCH 05/18] add timeout to image download --- kintree/common/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kintree/common/tools.py b/kintree/common/tools.py index 13276f4e..eefba132 100644 --- a/kintree/common/tools.py +++ b/kintree/common/tools.py @@ -67,7 +67,7 @@ def get_image_with_retries(url, headers, retries=3, wait=5, silent=False): scraper = cloudscraper.create_scraper() for attempt in range(retries): try: - response = scraper.get(url, headers=headers) + response = scraper.get(url, headers=headers, timeout=wait) if response.status_code == 200: return response else: From 70a8e457162342249972c9ac789774c7caf4b11d Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 21 Oct 2024 13:18:32 +0200 Subject: [PATCH 06/18] fix price break handling for digikey --- kintree/database/inventree_interface.py | 12 +++++++-- kintree/search/digikey_api.py | 35 +++++++++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/kintree/database/inventree_interface.py b/kintree/database/inventree_interface.py index bb992f42..f21f0a85 100644 --- a/kintree/database/inventree_interface.py +++ b/kintree/database/inventree_interface.py @@ -454,7 +454,7 @@ def get_value_from_user_key(user_key: str, default_key: str, default_value=None) part_form['manufacturer_part_number'] = get_value_from_user_key('SEARCH_MPN', default_search_keys[6], default_value='') part_form['datasheet'] = get_value_from_user_key('SEARCH_DATASHEET', default_search_keys[8], default_value='') part_form['image'] = get_value_from_user_key('', default_search_keys[9], default_value='') - + print(part_form) return part_form @@ -485,18 +485,25 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict: cprint(f'\n[MAIN]\t{supplier} search for {part_number}', silent=settings.SILENT) if supplier == 'Digi-Key': part_info = digikey_api.fetch_part_info(part_number) + print('Digikey', part_info) elif supplier == 'Mouser': part_info = mouser_api.fetch_part_info(part_number) + print('Mouser', part_info) elif supplier in ['Farnell', 'Newark', 'Element14']: part_info = element14_api.fetch_part_info(part_number, supplier) + print('Farnell', part_info) elif supplier == 'LCSC': part_info = lcsc_api.fetch_part_info(part_number) + print('LCSC', part_info) elif supplier == 'Jameco': part_info = jameco_api.fetch_part_info(part_number) + print('Jameco', part_info) elif supplier == 'TME': part_info = tme_api.fetch_part_info(part_number) + print('TME', part_info) elif supplier == 'AutomationDirect': part_info = automationdirect_api.fetch_part_info(part_number) + print('AutomationDirect', part_info) # Check supplier data exist if not part_info: @@ -707,6 +714,7 @@ def inventree_create(part_info: dict, stock=None, kicad=False, symbol=None, foot return new_part, part_pk, inventree_part # Create manufacturer part + print(inventree_part) if inventree_part['manufacturer_name'] and inventree_part['manufacturer_part_number']: # Overwrite manufacturer name with matching one from database manufacturer_name = inventree_fuzzy_company_match(inventree_part['manufacturer_name']) @@ -733,7 +741,7 @@ def inventree_create(part_info: dict, stock=None, kicad=False, symbol=None, foot if is_manufacturer_part_created: cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT) - + print(f'Hersteller: {manufacturer_name}') # Create supplier part if inventree_part['supplier_name'] and inventree_part['supplier_part_number']: # Overwrite manufacturer name with matching one from database diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py index 4c64a27d..e6ebb27d 100644 --- a/kintree/search/digikey_api.py +++ b/kintree/search/digikey_api.py @@ -25,6 +25,7 @@ 'standard_pricing', 'break_quantity', 'unit_price', + 'package_type' ] @@ -121,6 +122,13 @@ def digikey_search_timeout(): # return manufacturer_product_details.get('product_details', None)[0] # else: # return {} + # Method to process price breaks + def process_price_break(product_variation): + part_info['digi_key_part_number'] = product_variation.get(digi_number_key) + for price_break in product_variation[pricing_key]: + quantity = price_break[qty_key] + price = price_break[price_key] + part_info['pricing'][quantity] = price # Query part number try: @@ -175,17 +183,22 @@ def digikey_search_timeout(): digi_number_key, pricing_key, qty_key, - price_key] = PRICING_MAP - - for variation in part[variations_key]: - digi_number = variation.get(digi_number_key) - if not variation.get('digi_reel_fee'): - part_info['digi_key_part_number'] = digi_number - part_info['pricing'][digi_number] = {} - for price_break in variation[pricing_key]: - quantity = price_break[qty_key] - price = price_break[price_key] - part_info['pricing'][digi_number][quantity] = price + price_key, + package_key] = PRICING_MAP + + variations = part[variations_key] + if len(variations) == 1: + process_price_break(variations[0]) + else: + for variation in variations: + # we try to get the not TR or Digi-Reel option + package_type = variation.get(package_key).get('id') + if all(package_type != x for x in [1, 243]): + process_price_break(variation) + break + # if no other option was found use the first one returned + if not part_info['pricing'] and variations: + process_price_break(variations[0]) # Extra search fields if settings.CONFIG_DIGIKEY.get('EXTRA_FIELDS'): From c57417bdea77a125e7df176d2c20023623b3df39 Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 21 Oct 2024 13:43:47 +0200 Subject: [PATCH 07/18] include checks and dependencies for python3.12 --- .github/workflows/test_deploy.yaml | 4 ++-- kintree/database/inventree_interface.py | 12 ++---------- pyproject.toml | 14 +++++++------- tasks.py | 2 +- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test_deploy.yaml b/.github/workflows/test_deploy.yaml index 8f4de210..0b34655c 100644 --- a/.github/workflows/test_deploy.yaml +++ b/.github/workflows/test_deploy.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout code @@ -57,7 +57,7 @@ jobs: continue-on-error: true strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout code diff --git a/kintree/database/inventree_interface.py b/kintree/database/inventree_interface.py index f21f0a85..bb992f42 100644 --- a/kintree/database/inventree_interface.py +++ b/kintree/database/inventree_interface.py @@ -454,7 +454,7 @@ def get_value_from_user_key(user_key: str, default_key: str, default_value=None) part_form['manufacturer_part_number'] = get_value_from_user_key('SEARCH_MPN', default_search_keys[6], default_value='') part_form['datasheet'] = get_value_from_user_key('SEARCH_DATASHEET', default_search_keys[8], default_value='') part_form['image'] = get_value_from_user_key('', default_search_keys[9], default_value='') - print(part_form) + return part_form @@ -485,25 +485,18 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict: cprint(f'\n[MAIN]\t{supplier} search for {part_number}', silent=settings.SILENT) if supplier == 'Digi-Key': part_info = digikey_api.fetch_part_info(part_number) - print('Digikey', part_info) elif supplier == 'Mouser': part_info = mouser_api.fetch_part_info(part_number) - print('Mouser', part_info) elif supplier in ['Farnell', 'Newark', 'Element14']: part_info = element14_api.fetch_part_info(part_number, supplier) - print('Farnell', part_info) elif supplier == 'LCSC': part_info = lcsc_api.fetch_part_info(part_number) - print('LCSC', part_info) elif supplier == 'Jameco': part_info = jameco_api.fetch_part_info(part_number) - print('Jameco', part_info) elif supplier == 'TME': part_info = tme_api.fetch_part_info(part_number) - print('TME', part_info) elif supplier == 'AutomationDirect': part_info = automationdirect_api.fetch_part_info(part_number) - print('AutomationDirect', part_info) # Check supplier data exist if not part_info: @@ -714,7 +707,6 @@ def inventree_create(part_info: dict, stock=None, kicad=False, symbol=None, foot return new_part, part_pk, inventree_part # Create manufacturer part - print(inventree_part) if inventree_part['manufacturer_name'] and inventree_part['manufacturer_part_number']: # Overwrite manufacturer name with matching one from database manufacturer_name = inventree_fuzzy_company_match(inventree_part['manufacturer_name']) @@ -741,7 +733,7 @@ def inventree_create(part_info: dict, stock=None, kicad=False, symbol=None, foot if is_manufacturer_part_created: cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT) - print(f'Hersteller: {manufacturer_name}') + # Create supplier part if inventree_part['supplier_name'] and inventree_part['supplier_part_number']: # Overwrite manufacturer name with matching one from database diff --git a/pyproject.toml b/pyproject.toml index 123ad242..de50a1eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,18 +11,18 @@ repository = "https://github.com/sparkmicro/Ki-nTree" keywords = ["inventree", "kicad", "digikey", "mouser", "component", "part", "create"] [tool.poetry.dependencies] -python = ">=3.9,<3.12" -digikey-api = "^1.0.0" -# digikey-api = { git = "https://github.com/hurricaneJoef/digikey-api.git", branch = "master" } +python = ">=3.9,<=3.12" +# digikey-api = "^1.0.0" +digikey-api = { git = "https://github.com/hurricaneJoef/digikey-api.git", branch = "master" } flet = "^0.24.1" thefuzz = "^0.19.0" inventree = "^0.17.0" -kiutils = "^1.4.0" -mouser = "^0.1.3" -multiprocess = "^0.70.12" +kiutils = "^1.4.8" +mouser = "^0.1.6" +multiprocess = "^0.70.16" pyyaml = "^6.0.1" validators = "^0.19.0" -wrapt_timeout_decorator = "^1.3.12" +wrapt_timeout_decorator = "^1.5.1" cloudscraper = "^1.2.71" [tool.poetry.dev-dependencies] diff --git a/tasks.py b/tasks.py index 96068efa..1e7a3115 100644 --- a/tasks.py +++ b/tasks.py @@ -121,7 +121,7 @@ def python_badge(c): cprint('[MAIN]\tInstall pybadges') c.run('pip install pybadges pip-autoremove', hide=True) cprint('[MAIN]\tCreate badge') - c.run('python -m pybadges --left-text="python" --right-text="3.9 | 3.10 | 3.11" ' + c.run('python -m pybadges --left-text="python" --right-text="3.9 | 3.10 | 3.11 | 3.12" ' '--whole-link="https://www.python.org/" --browser --embed-logo ' '--logo="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/python.svg"') cprint('[MAIN]\tUninstall pybadges') From b1d2ef662396d936b4679cbe1a7d68131513149b Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 21 Oct 2024 15:00:01 +0200 Subject: [PATCH 08/18] replace strings with escape sequences with raw strings --- kintree/common/part_tools.py | 2 +- kintree/database/inventree_api.py | 9 +-------- kintree/search/automationdirect_api.py | 6 +++--- kintree/search/jameco_api.py | 4 ++-- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/kintree/common/part_tools.py b/kintree/common/part_tools.py index 14dc533f..76b00146 100644 --- a/kintree/common/part_tools.py +++ b/kintree/common/part_tools.py @@ -149,7 +149,7 @@ def clean_parameter_value(category: str, name: str, value: str) -> str: # Remove parenthesis section if '(' in value: - parenthesis = re.findall('\(.*\)', value) + parenthesis = re.findall(r'\(.*\)', value) if parenthesis: for item in parenthesis: diff --git a/kintree/database/inventree_api.py b/kintree/database/inventree_api.py index 13c3728f..c416a643 100644 --- a/kintree/database/inventree_api.py +++ b/kintree/database/inventree_api.py @@ -751,19 +751,12 @@ def create_supplier_part(part_id: int, manufacturer_name: str, manufacturer_mpn: return False, False -def sanitize_price(price_in): - price = re.findall('\d+.\d+', price_in)[0] - price = price.replace(',', '.') - price = price.replace('\xa0', '') - return price - - def update_price_breaks(supplier_part, price_breaks: dict, currency='USD') -> bool: ''' Update the Price Breaks associated with a supplier part ''' def sanitize_price(price_in): - price = re.findall('\d+.\d+', price_in)[0] + price = re.findall(r'\d+.\d+', price_in)[0] price = price.replace(',', '.') price = price.replace('\xa0', '') return price diff --git a/kintree/search/automationdirect_api.py b/kintree/search/automationdirect_api.py index a3e44af9..780c5e51 100644 --- a/kintree/search/automationdirect_api.py +++ b/kintree/search/automationdirect_api.py @@ -146,7 +146,7 @@ def search_timeout(timeout=10): parameter_name = parameter_name.replace('/', '') parameter_value = attribute_list[1] try: - html_li_list = re.split("]*\s*>|(\&(?:[\w\d]+|#\d+|#x[a-f\d]+);)", parameter_value) + html_li_list = re.split(r"]*\s*>|(\&(?:[\w\d]+|#\d+|#x[a-f\d]+);)", parameter_value) cleaned_html_li_list = list(filter(None, html_li_list)) parameter_value = ', '.join(cleaned_html_li_list) except Exception as e: @@ -156,7 +156,7 @@ def search_timeout(timeout=10): # Nominal Input Voltage gives range min-max, parse it out to put in min/max params if parameter_name == "Nominal Input Voltage": if parameter_value.count('-') == 1: - parameter_value = re.sub('[^\d-]+', '', parameter_value) + parameter_value = re.sub(r'[^\d-]+', '', parameter_value) values_list = parameter_value.split('-') min_value = min(values_list) max_value = max(values_list) @@ -169,7 +169,7 @@ def search_timeout(timeout=10): # Nominal Output Voltage gives range min-max, parse it out to put in min/max params if parameter_name == "Nominal Output Voltage": if parameter_value.count('-') == 1: - parameter_value = re.sub('[^\d-]+', '', parameter_value) + parameter_value = re.sub(r'[^\d-]+', '', parameter_value) values_list = parameter_value.split('-') min_value = min(values_list) max_value = max(values_list) diff --git a/kintree/search/jameco_api.py b/kintree/search/jameco_api.py index c1431624..1fd5d677 100644 --- a/kintree/search/jameco_api.py +++ b/kintree/search/jameco_api.py @@ -121,9 +121,9 @@ def search_timeout(timeout=10): # Convert pricing string pattern to List, then dictionary for Ki-nTree price_break_str = part['secondary_prices'] price_break_str = price_break_str.replace(',', '') # remove comma - price_break_str = re.sub('(\<br\s\/&*gt)', '', price_break_str) # remove HTML + price_break_str = re.sub(r'(\<br\s\/&*gt)', '', price_break_str) # remove HTML price_break_str = re.sub(';', ':', price_break_str) # remove ; char - price_break_str = re.sub('(\:\s+\$)|\;', ':', price_break_str) # remove $ char + price_break_str = re.sub(r'(\:\s+\$)|\;', ':', price_break_str) # remove $ char price_break_list = price_break_str.split(':') # split on : price_break_list.pop() # remove last empty element in List From cd374dfca7dc879eddeab4dccb523c4564b5228f Mon Sep 17 00:00:00 2001 From: T0jan Date: Tue, 22 Oct 2024 13:24:11 +0200 Subject: [PATCH 09/18] fix bug in digikey_api, update test data --- kintree/database/inventree_api.py | 1 + kintree/search/digikey_api.py | 18 +++++++++++------- run_tests.py | 4 ++-- tests/files/results.tgz | Bin 122880 -> 19206 bytes tests/test_samples.yaml | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/kintree/database/inventree_api.py b/kintree/database/inventree_api.py index c416a643..3d7e364b 100644 --- a/kintree/database/inventree_api.py +++ b/kintree/database/inventree_api.py @@ -831,6 +831,7 @@ def create_parameter_template(name: str, units: str) -> int: }) except: cprint(f'[TREE]\tError: Failed to create parameter template "{name}".', silent=settings.SILENT) + return 0 if parameter_template: return parameter_template.pk diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py index e6ebb27d..4480560e 100644 --- a/kintree/search/digikey_api.py +++ b/kintree/search/digikey_api.py @@ -79,10 +79,14 @@ def get_default_search_keys(): def find_categories(part_details: str): ''' Find categories ''' - try: - return part_details['limited_taxonomy'].get('value'), part_details['limited_taxonomy']['children'][0].get('value') - except: - return None, None + category = part_details.get('category') + subcategory = None + if category: + subcategory = category.get('child_categories')[0] + category = category.get('name') + if subcategory: + subcategory = subcategory.get('name') + return category, subcategory def fetch_part_info(part_number: str) -> dict: @@ -221,10 +225,10 @@ def test_api(check_content=False) -> bool: 'product_description': 'RES 10K OHM 5% 1/16W 0402', 'digi_key_part_number': 'RMCF0402JT10K0CT-ND', 'manufacturer': 'Stackpole Electronics Inc', - 'manufacturer_part_number': 'RMCF0402JT10K0', + 'manufacturer_product_number': 'RMCF0402JT10K0', 'product_url': 'https://www.digikey.com/en/products/detail/stackpole-electronics-inc/RMCF0402JT10K0/1758206', - 'primary_datasheet': 'https://www.seielect.com/catalog/sei-rmcf_rmcp.pdf', - 'primary_photo': 'https://mm.digikey.com/Volume0/opasdata/d220001/medias/images/2597/MFG_RMC SERIES.jpg', + 'datasheet_url': 'https://www.seielect.com/catalog/sei-rmcf_rmcp.pdf', + 'photo_url': 'https://mm.digikey.com/Volume0/opasdata/d220001/medias/images/2597/MFG_RMC SERIES.jpg', } test_part = fetch_part_info('RMCF0402JT10K0') diff --git a/run_tests.py b/run_tests.py index 00afbe2e..49aadab8 100644 --- a/run_tests.py +++ b/run_tests.py @@ -28,7 +28,7 @@ # Enable InvenTree tests ENABLE_INVENTREE = True # Enable KiCad tests -ENABLE_KICAD = True +ENABLE_KICAD = False # Set categories to test PART_CATEGORIES = [ 'Capacitors', @@ -221,7 +221,7 @@ def check_result(status: str, new_part: bool) -> bool: 'category_tree': [supplier_info['category'], supplier_info['subcategory']], 'parameters': supplier_info['parameters'], 'Symbol': f'{category}:{number}', - 'IPN': supplier_info['manufacturer_part_number'], + 'IPN': supplier_info['manufacturer_product_number'], }) # Update categories part_info['category_tree'] = inventree_interface.get_categories_from_supplier_data(part_info) diff --git a/tests/files/results.tgz b/tests/files/results.tgz index df05accceb43cf3eee27daff78033aef799aad1e..dd1c4d2422efca12d849b1a447eeb9508ae66ba9 100644 GIT binary patch literal 19206 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�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 diff --git a/tests/test_samples.yaml b/tests/test_samples.yaml index 0d9040ce..ab42fc6f 100644 --- a/tests/test_samples.yaml +++ b/tests/test_samples.yaml @@ -130,7 +130,7 @@ Resistors: RF: # Filter (Balun) - 2450BM14E0003T: original + 2450BM14E0003001T: original Transistors: # NPN From 7a9f36ce8f54c8af8255101eb020afde20e7a4d0 Mon Sep 17 00:00:00 2001 From: T0jan Date: Tue, 22 Oct 2024 13:28:29 +0200 Subject: [PATCH 10/18] reenable Kicad tests --- run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests.py b/run_tests.py index 49aadab8..36aa367c 100644 --- a/run_tests.py +++ b/run_tests.py @@ -28,7 +28,7 @@ # Enable InvenTree tests ENABLE_INVENTREE = True # Enable KiCad tests -ENABLE_KICAD = False +ENABLE_KICAD = True # Set categories to test PART_CATEGORIES = [ 'Capacitors', From 29cbd0ed343fb2f1c7ac75a324d96ce899642388 Mon Sep 17 00:00:00 2001 From: T0jan Date: Tue, 22 Oct 2024 17:17:29 +0200 Subject: [PATCH 11/18] add localization options to digikey API --- kintree/gui/views/settings.py | 18 ++++++++++++++++++ kintree/search/digikey_api.py | 12 +++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/kintree/gui/views/settings.py b/kintree/gui/views/settings.py index b4f4cb99..12ada8b2 100644 --- a/kintree/gui/views/settings.py +++ b/kintree/gui/views/settings.py @@ -43,6 +43,21 @@ ft.TextField(), None, ] + supplier_settings[supplier]['Local Site'] = [ + digikey_api_settings.get('DIGIKEY_LOCAL_SITE', 'US'), + ft.TextField(), + None, + ] + supplier_settings[supplier]['Language'] = [ + digikey_api_settings.get('DIGIKEY_LOCAL_LANGUAGE', 'en'), + ft.TextField(), + None, + ] + supplier_settings[supplier]['Currency'] = [ + digikey_api_settings.get('DIGIKEY_LOCAL_CURRENCY', 'USD'), + ft.TextField(), + None, + ] elif supplier == 'Mouser': mouser_api_settings = config_interface.load_file(global_settings.CONFIG_MOUSER_API) supplier_settings[supplier]['Part API Key'] = [ @@ -673,6 +688,9 @@ def save_s(self, e: ft.ControlEvent, supplier: str, show_dialog=True): updated_settings = { 'DIGIKEY_CLIENT_ID': SETTINGS[self.title][supplier]['Client ID'][1].value, 'DIGIKEY_CLIENT_SECRET': SETTINGS[self.title][supplier]['Client Secret'][1].value, + 'DIGIKEY_LOCAL_SITE': SETTINGS[self.title][supplier]['Local Site'][1].value, + 'DIGIKEY_LOCAL_LANGUAGE': SETTINGS[self.title][supplier]['Language'][1].value, + 'DIGIKEY_LOCAL_CURRENCY': SETTINGS[self.title][supplier]['Currency'][1].value, } digikey_settings = {**settings_from_file, **updated_settings} config_interface.dump_file(digikey_settings, global_settings.CONFIG_DIGIKEY_API) diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py index 4480560e..ffebc737 100644 --- a/kintree/search/digikey_api.py +++ b/kintree/search/digikey_api.py @@ -28,7 +28,6 @@ 'package_type' ] - os.environ['DIGIKEY_STORAGE_PATH'] = settings.DIGIKEY_STORAGE_PATH # Check if storage path exists, else create it if not os.path.exists(os.environ['DIGIKEY_STORAGE_PATH']): @@ -58,7 +57,9 @@ def setup_environment(force=False) -> bool: digikey_api_settings = config_interface.load_file(settings.CONFIG_DIGIKEY_API) os.environ['DIGIKEY_CLIENT_ID'] = digikey_api_settings['DIGIKEY_CLIENT_ID'] os.environ['DIGIKEY_CLIENT_SECRET'] = digikey_api_settings['DIGIKEY_CLIENT_SECRET'] - + os.environ['DIGIKEY_LOCAL_SITE'] = digikey_api_settings.get('DIGIKEY_LOCAL_SITE', 'US') + os.environ['DIGIKEY_LOCAL_LANGUAGE'] = digikey_api_settings.get('DIGIKEY_LOCAL_LANGUAGE', 'en') + os.environ['DIGIKEY_LOCAL_CURRENCY'] = digikey_api_settings.get('DIGIKEY_LOCAL_CURRENCY', 'USD') return check_environment() @@ -103,7 +104,12 @@ def fetch_part_info(part_number: str) -> dict: # Added logic to check the result in the GUI flow @timeout(dec_timeout=20) def digikey_search_timeout(): - return digikey.product_details(part_number).to_dict() + return digikey.product_details( + part_number, + x_digikey_locale_site=os.environ['DIGIKEY_LOCAL_SITE'], + x_digikey_locale_language=os.environ['DIGIKEY_LOCAL_LANGUAGE'], + x_digikey_locale_currency=os.environ['DIGIKEY_LOCAL_CURRENCY'], + ).to_dict() # THIS METHOD WILL NOT WORK WITH DIGI-KEY PART NUMBERS... # @timeout(dec_timeout=20) From b4322d36eeb0c798ea37d81e6efa685777d52074 Mon Sep 17 00:00:00 2001 From: T0jan <22519396+T0jan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:00:03 +0200 Subject: [PATCH 12/18] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a14cf39..4ac7e299 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,16 @@ Ki-nTree was developed by [@eeintech](https://github.com/eeintech) for [SPARK Mi ### Requirements -* Ki-nTree is currently tested for Python 3.9 to 3.11 versions. +* Ki-nTree is currently tested for Python 3.9 to 3.12 versions. * Ki-nTree requires a Digi-Key **production** API instance. To create one, go to https://developer.digikey.com/. Create an account, an organization and add a **production** API to your organization. Save both Client ID and Secret keys. > [Here is a video](https://youtu.be/OI1EGEc0Ju0) to help with the different steps * Ki-nTree requires a Mouser Search API key. To request one, head over to https://www.mouser.ca/api-search/ and click on "Sign Up for Search API" * Ki-nTree requires an Element14 Product Search API key to fetch part information for the following suppliers: Farnell (Europe), Newark (North America) and Element14 (Asia-Pacific). To request one, head over to https://partner.element14.com/ and click on "Register" +* on rolling release distributions like Arch Linux some Flet dependencies need to be repaired manually: +``` +sudo pacman -S mpv +sudo ln -s /usr/lib/libmpv.so /usr/lib/libmpv.so.1 +``` ### Installation (system wide) From 5ce744ada4c236828ae858a207da36af8e95ec24 Mon Sep 17 00:00:00 2001 From: T0jan Date: Wed, 23 Oct 2024 12:27:44 +0200 Subject: [PATCH 13/18] make whole top bar dragable --- kintree/gui/views/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kintree/gui/views/main.py b/kintree/gui/views/main.py index 84894cf4..b18d7020 100644 --- a/kintree/gui/views/main.py +++ b/kintree/gui/views/main.py @@ -36,7 +36,8 @@ maximizable=True, ), leading_width=40, - title=ft.WindowDragArea(ft.Text(f'Ki-nTree | {__version__}'), maximizable=True), + title=ft.WindowDragArea(ft.Container(ft.Text(f'Ki-nTree | {__version__}'), + width=10000), maximizable=True), center_title=False, bgcolor=ft.colors.SURFACE_VARIANT, actions=[], From c6c3d30dbb22c704f661423532bf67f4ffbcb211 Mon Sep 17 00:00:00 2001 From: T0jan Date: Fri, 25 Oct 2024 13:34:00 +0200 Subject: [PATCH 14/18] add LCSC supplier link generation --- kintree/search/lcsc_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kintree/search/lcsc_api.py b/kintree/search/lcsc_api.py index bc3f6ab0..2fc25b6b 100644 --- a/kintree/search/lcsc_api.py +++ b/kintree/search/lcsc_api.py @@ -31,7 +31,7 @@ def get_default_search_keys(): 'productCode', 'brandNameEn', 'productModel', - '', + 'part_url', 'pdfUrl', 'productImages', ] @@ -71,6 +71,10 @@ def search_timeout(timeout=10): if not part: return part_info + product_code = part.get('productCode') + if product_code: + part_info['part_url'] = f'https://www.lcsc.com/product-detail/{product_code}.html' + category, subcategory = find_categories(part) try: part_info['category'] = category From 407b068b1f8f5d4e7935c29df359a76d299a29a0 Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 28 Oct 2024 16:24:13 +0100 Subject: [PATCH 15/18] add handling of no part found to AutomationDirect API --- kintree/search/automationdirect_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kintree/search/automationdirect_api.py b/kintree/search/automationdirect_api.py index 780c5e51..b1e852d8 100644 --- a/kintree/search/automationdirect_api.py +++ b/kintree/search/automationdirect_api.py @@ -85,6 +85,8 @@ def search_timeout(timeout=10): else: cprint(f'[INFO]\tFound {part["numFound"]} results for "{part_number}", selecting first result', silent=False) part = part['docs'][0] # choose the first part in the returned returned list + else: + part = None except Exception as e: cprint(f'[INFO]\tError: fetch_part_info(): {repr(e)}') part = None @@ -189,7 +191,7 @@ def search_timeout(timeout=10): # Parse out ordering attributes pricing_attributes = {} - price_per_unit = part[price_key] + price_per_unit = part.get(price_key, '0') try: for attribute in part[ordering_attributes]: attribute = attribute.split(':') From fce6f64641bc2ce15b8d85bd2ae94ace9511daf4 Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 28 Oct 2024 16:28:00 +0100 Subject: [PATCH 16/18] handling for LCSC if unexpected or no response is returned --- kintree/search/lcsc_api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kintree/search/lcsc_api.py b/kintree/search/lcsc_api.py index 2fc25b6b..53a984d6 100644 --- a/kintree/search/lcsc_api.py +++ b/kintree/search/lcsc_api.py @@ -62,12 +62,11 @@ def search_timeout(timeout=10): # Query part number try: part = search_timeout() + # Extract result + part = part.get('result', None) except: part = {} - # Extract result - part = part.get('result', None) - if not part: return part_info From f063933829e2b6dc9f196b531861d58a30603276 Mon Sep 17 00:00:00 2001 From: T0jan Date: Mon, 28 Oct 2024 16:34:14 +0100 Subject: [PATCH 17/18] add drag area and close button to settings view --- kintree/gui/views/settings.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kintree/gui/views/settings.py b/kintree/gui/views/settings.py index 12ada8b2..9c75d5b4 100644 --- a/kintree/gui/views/settings.py +++ b/kintree/gui/views/settings.py @@ -306,7 +306,8 @@ # Settings AppBar settings_appbar = ft.AppBar( - title=ft.Text('Ki-nTree Settings'), + title=ft.WindowDragArea(ft.Container(ft.Text('Ki-nTree Settings'), + width=10000), maximizable=True), bgcolor=ft.colors.SURFACE_VARIANT ) @@ -374,6 +375,15 @@ def __init__(self, page: ft.Page): # Init view super().__init__(page=page, appbar=settings_appbar, navigation_rail=settings_navrail) + if not self.appbar.actions: + self.appbar.actions.extend( + [ + ft.IconButton( + ft.icons.CLOSE, + on_click=lambda _: page.window.close(), + ), + ] + ) # Update navigation rail self.navigation_rail.on_change = self.nav_rail_redirect From 954e13a32a4d10c78b07f9e19fc4b8da8ebf1982 Mon Sep 17 00:00:00 2001 From: T0jan Date: Tue, 29 Oct 2024 14:49:13 +0100 Subject: [PATCH 18/18] add missing requirements to pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index de50a1eb..87886767 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ keywords = ["inventree", "kicad", "digikey", "mouser", "component", "part", "cre python = ">=3.9,<=3.12" # digikey-api = "^1.0.0" digikey-api = { git = "https://github.com/hurricaneJoef/digikey-api.git", branch = "master" } +setuptools = "^75.2.0" flet = "^0.24.1" thefuzz = "^0.19.0" inventree = "^0.17.0"