• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sparkmicro / Ki-nTree / 4557294192

pending completion
4557294192

push

github

GitHub
Merge pull request #143 from sparkmicro/1.0.0rc

1525 of 1888 relevant lines covered (80.77%)

1.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.64
/kintree/database/inventree_interface.py
1
import copy
2✔
2

3
from ..config import settings
2✔
4
from ..common import part_tools, progress
2✔
5
from ..common.tools import cprint
2✔
6
from ..config import config_interface
2✔
7
from ..database import inventree_api
2✔
8
from ..search import search_api, digikey_api, mouser_api, element14_api, lcsc_api
2✔
9

10
category_separator = '/'
2✔
11

12

13
def connect_to_server(timeout=5) -> bool:
2✔
14
    ''' Connect to InvenTree server using user settings '''
15
    connect = False
2✔
16
    settings.load_inventree_settings()
2✔
17

18
    try:
2✔
19
        connect = inventree_api.connect(server=settings.SERVER_ADDRESS,
2✔
20
                                        username=settings.USERNAME,
21
                                        password=settings.PASSWORD,
22
                                        connect_timeout=timeout)
23
    except TimeoutError:
×
24
        pass
×
25

26
    if not connect:
2✔
27
        if not settings.SERVER_ADDRESS:
×
28
            cprint('[TREE]\tError connecting to InvenTree server: missing server address')
×
29
            return connect
×
30
        if not settings.USERNAME:
×
31
            cprint('[TREE]\tError connecting to InvenTree server: missing username')
×
32
            return connect
×
33
        if not settings.PASSWORD:
×
34
            cprint('[TREE]\tError connecting to InvenTree server: missing password')
×
35
            return connect
×
36
        cprint('[TREE]\tError connecting to InvenTree server: invalid address, username or password')
×
37
    else:
38
        env = [env_type.name for env_type in settings.Environment
2✔
39
               if env_type.value == settings.environment][0]
40
        cprint(f'[TREE]\tSuccessfully connected to InvenTree server (ENV={env})', silent=settings.SILENT)
2✔
41

42
    return connect
2✔
43

44

45
def category_tree(tree: str) -> str:
2✔
46
    import re
2✔
47
    find_prefix = re.match(r'^-+ (.+?)$', tree)
2✔
48
    if find_prefix:
2✔
49
        return find_prefix.group(1)
2✔
50
    return tree
2✔
51

52

53
def split_category_tree(tree: str) -> list:
2✔
54
    return category_tree(tree).split(category_separator)
×
55

56

57
def build_category_tree(reload=False, category=None) -> dict:
2✔
58
    '''Build InvenTree category tree from database data'''
59

60
    category_data = config_interface.load_file(settings.CONFIG_CATEGORIES)
2✔
61

62
    def build_tree(tree, left_to_go, level) -> list:
2✔
63
        try:
2✔
64
            last_entry = f' {category_tree(tree[-1])}{category_separator}'
2✔
65
        except IndexError:
2✔
66
            last_entry = ''
2✔
67
        if type(left_to_go) == dict:
2✔
68
            for key, value in left_to_go.items():
2✔
69
                tree.append(f'{"-" * level}{last_entry}{key}')
2✔
70
                build_tree(tree, value, level + 1)
2✔
71
        elif type(left_to_go) == list:
2✔
72
            # Supports legacy structure
73
            for item in left_to_go:
×
74
                tree.append(f'{"-" * level}{last_entry}{item}')
×
75
        elif left_to_go is None:
2✔
76
            pass
1✔
77
        return
2✔
78
    
79
    if reload:
2✔
80
        categories = inventree_api.get_categories()
2✔
81
        category_data.update({'CATEGORIES': categories})
2✔
82
        config_interface.dump_file(category_data, settings.CONFIG_CATEGORIES)
2✔
83
    else:
84
        categories = category_data.get('CATEGORIES', {})
2✔
85

86
    # Get specified branch
87
    if category:
2✔
88
        categories = {category: categories.get(category, {})}
2✔
89

90
    inventree_categories = []
2✔
91
    # Build category tree
92
    build_tree(inventree_categories, categories, 0)
2✔
93

94
    return inventree_categories
2✔
95

96

97
def get_categories_from_supplier_data(part_info: dict, supplier_only=False) -> list:
2✔
98
    ''' Find categories from part supplier data, use "somewhat automatic" matching '''
99
    from thefuzz import fuzz
2✔
100
    
101
    categories = [None, None]
2✔
102

103
    try:
2✔
104
        supplier_category = str(part_info['category_tree'][0])
2✔
105
        supplier_subcategory = str(part_info['category_tree'][1])
2✔
106
    except KeyError:
×
107
        return categories
×
108

109
    # Return supplier category, if match not needed
110
    if supplier_only:
2✔
111
        categories[0] = supplier_category
×
112
        categories[1] = supplier_subcategory
×
113
        return categories
×
114

115
    function_filter = False
2✔
116
    # TODO: Make 'filter_parameter' user defined?
117
    filter_parameter = 'Function Type'
2✔
118

119
    # Check existing matches
120
    # Load inversed category map
121
    category_map = config_interface.load_supplier_categories_inversed(supplier_config_path=settings.CONFIG_DIGIKEY_CATEGORIES)
2✔
122

123
    try:
2✔
124
        for inventree_category in category_map.keys():
2✔
125
            for key, inventree_subcategory in category_map[inventree_category].items():
2✔
126
                if supplier_subcategory == key:
2✔
127
                    categories[0] = inventree_category
2✔
128
                    # Check if filtering by function
129
                    if inventree_subcategory.startswith(config_interface.FUNCTION_FILTER_KEY):
2✔
130
                        function_filter = True
2✔
131

132
                    # Save subcategory if not function filtered
133
                    if not function_filter:
2✔
134
                        categories[1] = inventree_subcategory
2✔
135

136
                    break
2✔
137
    except:
×
138
        pass
×
139

140
    # Function Filter
141
    if not categories[1] and function_filter:
2✔
142
        cprint(f'[INFO]\tSubcategory is filtered using "{filter_parameter}" parameter', silent=settings.SILENT, end='')
2✔
143
        # Load parameter map
144
        parameter_map = config_interface.load_category_parameters(categories[0], settings.CONFIG_SUPPLIER_PARAMETERS)
2✔
145
        # Build compare list
146
        compare = []
2✔
147
        for supplier_parameter, inventree_parameter in parameter_map.items():
2✔
148
            if (supplier_parameter in part_info['parameters'].keys() and inventree_parameter == filter_parameter):
2✔
149
                compare.append(part_info['parameters'][supplier_parameter])
2✔
150

151
        # Load subcategory map
152
        category_map = config_interface.load_supplier_categories(supplier_config_path=settings.CONFIG_DIGIKEY_CATEGORIES)[categories[0]]
2✔
153
        for inventree_subcategory in category_map.keys():
2✔
154
            for item in compare:
2✔
155
                fuzzy_match = fuzz.partial_ratio(inventree_subcategory, item)
2✔
156
                display_result = f'"{inventree_subcategory}" ?= "{item}"'.ljust(50)
2✔
157
                cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG)
2✔
158
                if fuzzy_match >= settings.CATEGORY_MATCH_RATIO_LIMIT:
2✔
159
                    categories[1] = inventree_subcategory.replace(config_interface.FUNCTION_FILTER_KEY, '')
2✔
160
                    break
2✔
161

162
            if categories[1]:
2✔
163
                cprint('\t[ PASS ]', silent=settings.SILENT)
2✔
164
                break
2✔
165

166
    if not categories[1] and function_filter:
2✔
167
        cprint('\t[ FAILED ]', silent=settings.SILENT)
×
168

169
    # Automatic Match
170
    if not (categories[0] and categories[1]):
2✔
171
        # Load category map
172
        category_map = config_interface.load_supplier_categories(supplier_config_path=settings.CONFIG_DIGIKEY_CATEGORIES)
2✔
173

174
        def find_supplier_category_match(supplier_category: str, ignore_categories=False):
2✔
175
            # Check for match with Inventree categories
176
            category_match = None
2✔
177
            subcategory_match = None
2✔
178

179
            for inventree_category in category_map.keys():
2✔
180
                fuzzy_match = 0
2✔
181
                
182
                if not ignore_categories:
2✔
183
                    fuzzy_match = fuzz.partial_ratio(supplier_category, inventree_category)
2✔
184
                    display_result = f'"{supplier_category}" ?= "{inventree_category}"'.ljust(50)
2✔
185
                    cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG)
2✔
186

187
                if fuzzy_match < settings.CATEGORY_MATCH_RATIO_LIMIT and category_map[inventree_category]:
2✔
188
                    # Compare to subcategories
189
                    for inventree_subcategory in category_map[inventree_category]:
2✔
190
                        fuzzy_match = fuzz.partial_ratio(supplier_category, inventree_subcategory)
2✔
191
                        display_result = f'"{supplier_category}" ?= "{inventree_subcategory}"'.ljust(50)
2✔
192
                        cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG)
2✔
193

194
                        if fuzzy_match >= settings.CATEGORY_MATCH_RATIO_LIMIT:
2✔
195
                            subcategory_match = inventree_subcategory
2✔
196
                            break
2✔
197

198
                if fuzzy_match >= settings.CATEGORY_MATCH_RATIO_LIMIT:
2✔
199
                    category_match = inventree_category
2✔
200
                    break
2✔
201

202
            return category_match, subcategory_match
2✔
203

204
        # Find category and subcategories match
205
        category, subcategory = find_supplier_category_match(supplier_category)
2✔
206
        if category:
2✔
207
            categories[0] = category
2✔
208
        if subcategory:
2✔
209
            categories[1] = subcategory
×
210

211
        # Run match with supplier subcategory
212
        if not categories[0] or not categories[1]:
2✔
213
            if categories[0]:
2✔
214
                # If category was found: ignore them for the comparison
215
                category, subcategory = find_supplier_category_match(supplier_subcategory, ignore_categories=True)
2✔
216
            else:
217
                category, subcategory = find_supplier_category_match(supplier_subcategory)
×
218

219
        if category and not categories[0]:
2✔
220
            categories[0] = category
×
221
        if subcategory and not categories[1]:
2✔
222
            categories[1] = subcategory
2✔
223

224
    # Final checks
225
    if not categories[0]:
2✔
226
        cprint(f'[INFO]\tWarning: "{part_info["category_tree"][0]}" did not match any supplier category ', silent=settings.SILENT)
×
227
    else:
228
        cprint(f'[INFO]\tCategory: "{categories[0]}"', silent=settings.SILENT)
2✔
229
    if not categories[1]:
2✔
230
        cprint(f'[INFO]\tWarning: "{part_info["category_tree"][1]}" did not match any supplier subcategory ', silent=settings.SILENT)
×
231
    else:
232
        cprint(f'[INFO]\tSubcategory: "{categories[1]}"', silent=settings.SILENT)
2✔
233
    
234
    # print(f'{supplier_category=} | {supplier_subcategory=} | {categories[0]=} | {categories[1]=}')
235
    return categories
2✔
236

237

238
def translate_form_to_inventree(part_info: dict, category_tree: list, is_custom=False) -> dict:
2✔
239
    ''' Using supplier part data and categories, fill-in InvenTree part dictionary '''
240

241
    # Copy template
242
    inventree_part = copy.deepcopy(settings.inventree_part_template)
2✔
243

244
    # Translate form data to inventree part
245
    inventree_part['category_tree'] = category_tree
2✔
246
    inventree_part['name'] = part_info['name']
2✔
247
    inventree_part['description'] = part_info['description']
2✔
248
    inventree_part['revision'] = part_info['revision']
2✔
249
    inventree_part['keywords'] = part_info['keywords']
2✔
250
    inventree_part['supplier_name'] = part_info['supplier_name']
2✔
251
    inventree_part['supplier_part_number'] = part_info['supplier_part_number']
2✔
252
    inventree_part['manufacturer_name'] = part_info['manufacturer_name']
2✔
253
    inventree_part['manufacturer_part_number'] = part_info['manufacturer_part_number']
2✔
254
    inventree_part['IPN'] = part_info.get('IPN', '')
2✔
255
    # Replace whitespaces in URL
256
    inventree_part['supplier_link'] = part_info['supplier_link'].replace(' ', '%20')
2✔
257
    inventree_part['datasheet'] = part_info['datasheet'].replace(' ', '%20')
2✔
258
    # Image URL is not shown to user so force default key/value
259
    inventree_part['image'] = part_info['image'].replace(' ', '%20')
2✔
260

261
    # Load parameters map
262
    if category_tree:
2✔
263
        parameter_map = config_interface.load_category_parameters(
2✔
264
            category=category_tree[0],
265
            supplier_config_path=settings.CONFIG_SUPPLIER_PARAMETERS,
266
        )
267
    else:
268
        cprint('[INFO]\tWarning: Parameter map not loaded (no category selected)', silent=settings.SILENT)
×
269

270
    if not is_custom:
2✔
271
        # Add Parameters
272
        if parameter_map:
2✔
273
            for supplier_param, inventree_param in parameter_map.items():
2✔
274
                # Some parameters may not be mapped
275
                if inventree_param not in inventree_part['parameters'].keys():
2✔
276
                    if supplier_param != 'Manufacturer Part Number':
2✔
277
                        try:
2✔
278
                            parameter_value = part_tools.clean_parameter_value(
2✔
279
                                category=category_tree[0],
280
                                name=supplier_param,
281
                                value=part_info['parameters'][supplier_param],
282
                            )
283
                            inventree_part['parameters'][inventree_param] = parameter_value
2✔
284
                        except:
2✔
285
                            cprint(f'[INFO]\tWarning: Parameter "{supplier_param}" not found in supplier data', silent=settings.SILENT)
2✔
286
                    else:
287
                        inventree_part['parameters'][inventree_param] = part_info['manufacturer_part_number']
2✔
288

289
            # Check for missing parameters and fill value with dash
290
            for inventree_param in parameter_map.values():
2✔
291
                if inventree_param not in inventree_part['parameters'].keys():
2✔
292
                    inventree_part['parameters'][inventree_param] = '-'
2✔
293
        else:
294
            cprint(f'[INFO]\tWarning: Parameter map for "{category_tree[0]}" does not exist or is empty', silent=settings.SILENT)
×
295

296
    return inventree_part
2✔
297

298

299
def get_supplier_name(supplier: str) -> str:
2✔
300
    ''' Get InvenTree supplier name '''
301

302
    supplier_name = supplier
×
303

304
    for supplier, data in settings.CONFIG_SUPPLIERS.items():
×
305
        if data['name'] == supplier_name:
×
306
            # Update supplier name
307
            supplier_name = supplier
×
308
            break
×
309
    
310
    return supplier_name
×
311

312

313
def translate_supplier_to_form(supplier: str, part_info: dict) -> dict:
2✔
314
    ''' Translate supplier data to user form format '''
315

316
    part_form = {}
2✔
317

318
    def get_value_from_user_key(user_key: str, default_key: str, default_value=None) -> str:
2✔
319
        ''' Get value mapped from user search key, else default search key '''
320
        user_search_key = None
2✔
321
        if supplier == 'Digi-Key':
2✔
322
            user_search_key = settings.CONFIG_DIGIKEY.get(user_key, None)
2✔
323
        elif supplier == 'Mouser':
×
324
            user_search_key = settings.CONFIG_MOUSER.get(user_key, None)
×
325
        elif supplier in ['Farnell', 'Newark', 'Element14']:
×
326
            user_search_key = settings.CONFIG_ELEMENT14.get(user_key, None)
×
327
        elif supplier == 'LCSC':
×
328
            user_search_key = settings.CONFIG_LCSC.get(user_key, None)
×
329
        else:
330
            return default_value
×
331
        
332
        # If no user key, use default
333
        if not user_search_key:
2✔
334
            return part_info.get(default_key, default_value)
2✔
335

336
        # Get value for user key, return value from default key if not found
337
        return part_info.get(user_search_key, part_info.get(default_key, default_value))
×
338

339
    # Check that supplier argument is valid
340
    if not supplier and supplier != 'custom':
2✔
341
        return part_form
×
342
    # Get default keys
343
    if supplier == 'Digi-Key':
2✔
344
        default_search_keys = digikey_api.get_default_search_keys()
2✔
345
    elif supplier == 'Mouser':
×
346
        default_search_keys = mouser_api.get_default_search_keys()
×
347
    elif supplier in ['Farnell', 'Newark', 'Element14']:
×
348
        default_search_keys = element14_api.get_default_search_keys()
×
349
    elif supplier == 'LCSC':
×
350
        default_search_keys = lcsc_api.get_default_search_keys()
×
351
    else:
352
        # Empty array of default search keys
353
        default_search_keys = [''] * len(digikey_api.get_default_search_keys())
×
354

355
    # Default revision
356
    revision = settings.CONFIG_IPN.get('INVENTREE_DEFAULT_REV', '')
2✔
357
    # Translate supplier data to form fields
358
    part_form['name'] = get_value_from_user_key('SEARCH_NAME', default_search_keys[0], default_value='')
2✔
359
    part_form['description'] = get_value_from_user_key('SEARCH_DESCRIPTION', default_search_keys[1], default_value='')
2✔
360
    part_form['revision'] = get_value_from_user_key('SEARCH_REVISION', default_search_keys[2], default_value=revision)
2✔
361
    part_form['keywords'] = get_value_from_user_key('SEARCH_KEYWORDS', default_search_keys[1], default_value='')
2✔
362
    part_form['supplier_name'] = settings.CONFIG_SUPPLIERS[supplier]['name']
2✔
363
    part_form['supplier_part_number'] = get_value_from_user_key('SEARCH_SKU', default_search_keys[4], default_value='')
2✔
364
    part_form['supplier_link'] = get_value_from_user_key('SEARCH_SUPPLIER_URL', default_search_keys[7], default_value='')
2✔
365
    part_form['manufacturer_name'] = get_value_from_user_key('SEARCH_MANUFACTURER', default_search_keys[5], default_value='')
2✔
366
    part_form['manufacturer_part_number'] = get_value_from_user_key('SEARCH_MPN', default_search_keys[6], default_value='')
2✔
367
    part_form['datasheet'] = get_value_from_user_key('SEARCH_DATASHEET', default_search_keys[8], default_value='')
2✔
368
    part_form['image'] = get_value_from_user_key('', default_search_keys[9], default_value='')
2✔
369
    
370
    return part_form
2✔
371

372

373
def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:
2✔
374
    ''' Wrapper for supplier search, allow use of cached data (limited daily API calls) '''
375
    part_info = {}
2✔
376
    # Check part number exist
377
    if not part_number:
2✔
378
        cprint('\n[MAIN]\tError: Missing Part Number', silent=settings.SILENT)
2✔
379
        return part_info
2✔
380

381
    store = ''
2✔
382
    if supplier in ['Farnell', 'Newark', 'Element14']:
2✔
383
        element14_config = config_interface.load_file(settings.CONFIG_ELEMENT14_API)
×
384
        store = element14_config.get(f'{supplier.upper()}_STORE', '').replace(' ', '')
×
385
    search_filename = f"{settings.search_results['directory']}{supplier}{store}_{part_number}{settings.search_results['extension']}"
2✔
386
    # Get cached data, if cache is enabled (else returns None)
387
    part_cache = search_api.load_from_file(search_filename, test_mode)
2✔
388

389
    if part_cache:
2✔
390
        cprint(f'\n[MAIN]\tUsing {supplier} cached data for {part_number}', silent=settings.SILENT)
2✔
391
        part_info = part_cache
2✔
392
    else:
393
        cprint(f'\n[MAIN]\t{supplier} search for {part_number}', silent=settings.SILENT)
×
394
        if supplier == 'Digi-Key':
×
395
            part_info = digikey_api.fetch_part_info(part_number)
×
396
        elif supplier == 'Mouser':
×
397
            part_info = mouser_api.fetch_part_info(part_number)
×
398
        elif supplier in ['Farnell', 'Newark', 'Element14']:
×
399
            part_info = element14_api.fetch_part_info(part_number, supplier)
×
400
        elif supplier == 'LCSC':
×
401
            part_info = lcsc_api.fetch_part_info(part_number)
×
402

403
    # Check supplier data exist
404
    if not part_info:
2✔
405
        cprint(f'[INFO]\tError: Failed to fetch data for "{part_number}"', silent=settings.SILENT)
×
406

407
    # Save search results
408
    if part_info:
2✔
409
        update_ts = not bool(part_cache) or test_mode
2✔
410
        search_api.save_to_file(part_info, search_filename, update_ts=update_ts)
2✔
411

412
    return part_info
2✔
413

414

415
def inventree_fuzzy_company_match(name: str) -> str:
2✔
416
    ''' Fuzzy match company name to exisiting companies '''
417
    from thefuzz import fuzz
2✔
418
    
419
    inventree_companies = inventree_api.get_all_companies()
2✔
420

421
    for company_name in inventree_companies.keys():
2✔
422
        cprint(f'{name.lower()} == {company_name.lower()} % {fuzz.partial_ratio(name.lower(), company_name.lower())}',
2✔
423
               silent=settings.HIDE_DEBUG)
424
        if fuzz.partial_ratio(name.lower(), company_name.lower()) == 100:
2✔
425
            return company_name
2✔
426
    
427
    return name
2✔
428

429

430
def inventree_create_manufacturer_part(part_id: int, manufacturer_name: str, manufacturer_mpn: str, datasheet: str, description: str) -> bool:
2✔
431
    ''' Create manufacturer part '''
432

433
    cprint('\n[MAIN]\tCreating manufacturer part', silent=settings.SILENT)
2✔
434
    manufacturer_part = inventree_api.is_new_manufacturer_part(manufacturer_name=manufacturer_name,
2✔
435
                                                               manufacturer_mpn=manufacturer_mpn)
436

437
    if manufacturer_part:
2✔
438
        cprint('[INFO]\tManufacturer part already exists, skipping.', silent=settings.SILENT)
2✔
439
    else:
440
        # Create a new manufacturer part
441
        is_manufacturer_part_created = inventree_api.create_manufacturer_part(part_id=part_id,
2✔
442
                                                                              manufacturer_name=manufacturer_name,
443
                                                                              manufacturer_mpn=manufacturer_mpn,
444
                                                                              datasheet=datasheet,
445
                                                                              description=description)
446

447
        if is_manufacturer_part_created:
2✔
448
            cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT)
2✔
449
            return True
2✔
450

451
    return False
2✔
452

453

454
def inventree_create_supplier_part(part) -> bool:
2✔
455
    return
×
456

457

458
def inventree_create(part_info: dict, kicad=False, symbol=None, footprint=None, show_progress=True, is_custom=False):
2✔
459
    ''' Create InvenTree part from supplier part data and categories '''
460

461
    part_pk = 0
2✔
462
    new_part = False
2✔
463

464
    category_tree = part_info['category_tree']
2✔
465
    if not category_tree:
2✔
466
        cprint(f'[INFO]\tError: Category tree is empty {category_tree=}', silent=settings.SILENT)
×
467
        return new_part, part_pk, {}
×
468

469
    # Translate to InvenTree part format
470
    inventree_part = translate_form_to_inventree(
2✔
471
        part_info=part_info,
472
        category_tree=category_tree,
473
        is_custom=is_custom,
474
    )
475

476
    if not inventree_part:
2✔
477
        cprint('\n[MAIN]\tError: Failed to process form data', silent=settings.SILENT)
×
478

479
    category_pk = inventree_api.get_inventree_category_id(category_tree)
2✔
480
    if category_pk <= 0:
2✔
481
        cprint(f'[ERROR]\tCategory ({category_tree}) does not exist in InvenTree', silent=settings.SILENT)
×
482
    else:
483
        # Check if part already exists
484
        part_pk = inventree_api.is_new_part(category_pk, inventree_part)
2✔
485
        # Part exists
486
        if part_pk > 0:
2✔
487
            cprint('[INFO]\tPart already exists, skipping.', silent=settings.SILENT)
2✔
488
            ipn = inventree_api.get_part_number(part_pk)
2✔
489
            if ipn:
2✔
490
                # Update InvenTree part number
491
                inventree_part['IPN'] = ipn
2✔
492
                # Update InvenTree URL
493
                inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{inventree_part["IPN"]}/'
2✔
494
            else:
495
                inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{part_pk}/'
×
496
        # Part is new
497
        else:
498
            new_part = True
2✔
499
            # Create a new Part
500
            # Use the pk (primary-key) of the category
501
            part_pk = inventree_api.create_part(category_id=category_pk,
2✔
502
                                                name=inventree_part['name'],
503
                                                description=inventree_part['description'],
504
                                                revision=inventree_part['revision'],
505
                                                image=inventree_part['image'],
506
                                                keywords=inventree_part['keywords'])
507

508
            # Check part primary key
509
            if not part_pk:
2✔
510
                return new_part, part_pk, inventree_part
×
511
            # Progress Update
512
            if not progress.update_progress_bar(show_progress):
2✔
513
                return new_part, part_pk, inventree_part
×
514

515
            if settings.CONFIG_IPN.get('IPN_ENABLE_CREATE', True):
2✔
516
                # Generate Internal Part Number
517
                cprint('\n[MAIN]\tGenerating Internal Part Number', silent=settings.SILENT)
2✔
518
                ipn = part_tools.generate_part_number(
2✔
519
                    category=category_tree[0],
520
                    part_pk=part_pk,
521
                    category_code=part_info.get('category_code', ''),
522
                )
523
                cprint(f'[INFO]\tInternal Part Number = {ipn}', silent=settings.SILENT)
2✔
524
                # Update InvenTree part number
525
                ipn_update = inventree_api.set_part_number(part_pk, ipn)
2✔
526
                if not ipn_update:
2✔
527
                    cprint('\n[INFO]\tError updating IPN', silent=settings.SILENT)
×
528
                inventree_part['IPN'] = ipn
2✔
529
                # Update InvenTree URL
530
                inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{inventree_part["IPN"]}/'
2✔
531
            else:
532
                inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{part_pk}/'
×
533

534
    # Progress Update
535
    if not progress.update_progress_bar(show_progress):
2✔
536
        return new_part, part_pk, inventree_part
×
537

538
    if part_pk > 0:
2✔
539
        if new_part:
2✔
540
            cprint('[INFO]\tSuccess: Added new part to InvenTree', silent=settings.SILENT)
2✔
541
            if inventree_part['image']:
2✔
542
                # Add image
543
                image_result = inventree_api.upload_part_image(inventree_part['image'], part_pk)
2✔
544
                if not image_result:
2✔
545
                    cprint('[TREE]\tWarning: Failed to upload part image', silent=settings.SILENT)
×
546

547
        if kicad:
2✔
548
            # Create symbol & footprint parameters
549
            if symbol:
2✔
550
                symbol = f'{symbol.split(":")[0]}:{ipn}'
2✔
551
                inventree_part['parameters']['Symbol'] = symbol
2✔
552
            if footprint:
2✔
553
                inventree_part['parameters']['Footprint'] = footprint
×
554

555
        if not inventree_part['parameters']:
2✔
556
            category_parameters = inventree_api.get_category_parameters(category_pk)
×
557

558
            # Add category-defined parameters
559
            for parameter in category_parameters:
×
560
                inventree_part['parameters'][parameter[0]] = parameter[1]
×
561

562
        # Create parameters
563
        if len(inventree_part['parameters']) > 0:
2✔
564
            cprint('\n[MAIN]\tCreating parameters', silent=settings.SILENT)
2✔
565
            parameters_lists = [
2✔
566
                [],  # Store new parameters
567
                [],  # Store existings parameters
568
            ]
569
            for name, value in inventree_part['parameters'].items():
2✔
570
                parameter, is_new_parameter = inventree_api.create_parameter(part_id=part_pk, template_name=name, value=value)
2✔
571
                # Progress Update
572
                if not progress.update_progress_bar(show_progress, increment=0.03):
2✔
573
                    return new_part, part_pk, inventree_part
×
574

575
                if is_new_parameter:
2✔
576
                    parameters_lists[0].append(name)
2✔
577
                else:
578
                    parameters_lists[1].append(name)
2✔
579

580
            if parameters_lists[0]:
2✔
581
                cprint('[INFO]\tSuccess: The following parameters were created:', silent=settings.SILENT)
2✔
582
                for item in parameters_lists[0]:
2✔
583
                    cprint(f'--->\t{item}', silent=settings.SILENT)
2✔
584
            if parameters_lists[1]:
2✔
585
                cprint('[TREE]\tWarning: The following parameters were skipped:', silent=settings.SILENT)
2✔
586
                for item in parameters_lists[1]:
2✔
587
                    cprint(f'--->\t{item}', silent=settings.SILENT)
2✔
588

589
        # Create manufacturer part
590
        if inventree_part['manufacturer_name'] and inventree_part['manufacturer_part_number']:
2✔
591
            # Overwrite manufacturer name with matching one from database
592
            manufacturer_name = inventree_fuzzy_company_match(inventree_part['manufacturer_name'])
2✔
593
            # Get MPN
594
            manufacturer_mpn = inventree_part['manufacturer_part_number']
2✔
595

596
            cprint('\n[MAIN]\tCreating manufacturer part', silent=settings.SILENT)
2✔
597
            manufacturer_part = inventree_api.is_new_manufacturer_part(
2✔
598
                manufacturer_name=manufacturer_name,
599
                manufacturer_mpn=manufacturer_mpn,
600
            )
601

602
            if manufacturer_part:
2✔
603
                cprint('[INFO]\tManufacturer part already exists, skipping.', silent=settings.SILENT)
×
604
            else:
605
                # Create a new manufacturer part
606
                is_manufacturer_part_created = inventree_api.create_manufacturer_part(
2✔
607
                    part_id=part_pk,
608
                    manufacturer_name=manufacturer_name,
609
                    manufacturer_mpn=manufacturer_mpn,
610
                    datasheet=inventree_part['datasheet'],
611
                    description=inventree_part['description'],
612
                )
613

614
                if is_manufacturer_part_created:
2✔
615
                    cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT)
2✔
616

617
        # Create supplier part
618
        if inventree_part['supplier_name'] and inventree_part['supplier_part_number']:
2✔
619
            # Overwrite manufacturer name with matching one from database
620
            supplier_name = inventree_fuzzy_company_match(inventree_part['supplier_name'])
2✔
621
            # Get SKU
622
            supplier_sku = inventree_part['supplier_part_number']
2✔
623

624
            cprint('\n[MAIN]\tCreating supplier part', silent=settings.SILENT)
2✔
625
            is_new_supplier_part = inventree_api.is_new_supplier_part(supplier_name=supplier_name,
2✔
626
                                                                      supplier_sku=supplier_sku)
627

628
            if not is_new_supplier_part:
2✔
629
                cprint('[INFO]\tSupplier part already exists, skipping.', silent=settings.SILENT)
×
630
            else:
631
                # Create a new supplier part
632
                is_supplier_part_created = inventree_api.create_supplier_part(
2✔
633
                    part_id=part_pk,
634
                    manufacturer_name=manufacturer_name,
635
                    manufacturer_mpn=manufacturer_mpn,
636
                    supplier_name=supplier_name,
637
                    supplier_sku=supplier_sku,
638
                    description=inventree_part['description'],
639
                    link=inventree_part['supplier_link'],
640
                )
641

642
                if is_supplier_part_created:
2✔
643
                    cprint('[INFO]\tSuccess: Added new supplier part', silent=settings.SILENT)
2✔
644

645
    # Progress Update
646
    if not progress.update_progress_bar(show_progress):
2✔
647
        pass
648

649
    return new_part, part_pk, inventree_part
2✔
650

651

652
def inventree_create_alternate(part_info: dict, part_id='', part_ipn='', show_progress=None) -> bool:
2✔
653
    ''' Create alternate manufacturer and supplier entries for an existing InvenTree part '''
654

655
    result = False
2✔
656
    cprint('\n[MAIN]\tSearching for original part in database', silent=settings.SILENT)
2✔
657
    part = inventree_api.fetch_part(part_id, part_ipn)
2✔
658

659
    if part:
2✔
660
        part_pk = part.pk
2✔
661
        part_description = part.description
2✔
662
        cprint(f'[INFO] Success: Found original part in database (ID = {part_pk} | Description = "{part_description}")', silent=settings.SILENT)
2✔
663
    else:
664
        cprint('[INFO] Error: Original part was not found in database', silent=settings.SILENT)
×
665
        return result
×
666

667
    # Overwrite manufacturer name with matching one from database
668
    manufacturer_name = inventree_fuzzy_company_match(part_info.get('manufacturer_name', ''))
2✔
669
    manufacturer_mpn = part_info.get('manufacturer_part_number', '')
2✔
670
    datasheet = part_info.get('datasheet', '')
2✔
671

672
    # Create manufacturer part
673
    if manufacturer_name and manufacturer_mpn:
2✔
674
        inventree_create_manufacturer_part(part_id=part_pk,
2✔
675
                                           manufacturer_name=manufacturer_name,
676
                                           manufacturer_mpn=manufacturer_mpn,
677
                                           datasheet=datasheet,
678
                                           description=part_description)
679
    else:
680
        cprint('[INFO]\tWarning: No manufacturer part to create', silent=settings.SILENT)
×
681

682
    # Progress Update
683
    if not progress.update_progress_bar(show_progress, increment=0.2):
2✔
684
        return
×
685

686
    supplier_name = part_info.get('supplier_name', '')
2✔
687
    supplier_sku = part_info.get('supplier_part_number', '')
2✔
688
    supplier_link = part_info.get('supplier_link', '')
2✔
689

690
    # Add supplier alternate
691
    if supplier_name and supplier_sku:
2✔
692
        cprint('\n[MAIN]\tCreating supplier part', silent=settings.SILENT)
2✔
693
        is_new_supplier_part = inventree_api.is_new_supplier_part(supplier_name=supplier_name,
2✔
694
                                                                  supplier_sku=supplier_sku)
695

696
        if not is_new_supplier_part:
2✔
697
            cprint('[INFO]\tSupplier part already exists, skipping.', silent=settings.SILENT)
2✔
698
        else:
699
            # Create a new supplier part
700
            is_supplier_part_created = inventree_api.create_supplier_part(part_id=part_pk,
2✔
701
                                                                          manufacturer_name=manufacturer_name,
702
                                                                          manufacturer_mpn=manufacturer_mpn,
703
                                                                          supplier_name=supplier_name,
704
                                                                          supplier_sku=supplier_sku,
705
                                                                          description=part_description,
706
                                                                          link=supplier_link)
707

708
            if is_supplier_part_created:
2✔
709
                cprint('[INFO]\tSuccess: Added new supplier part', silent=settings.SILENT)
2✔
710
                result = True
2✔
711
    else:
712
        cprint('[INFO]\tWarning: No supplier part to create', silent=settings.SILENT)
×
713

714
    return result
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc