Skip to content

Commit 801fb94

Browse files
author
bosd
committed
Fix pre-commit issues and reduce function complexity in import_threaded.py\n\n- Refactor _create_batch_individually to reduce complexity from 17 to under 10\n- Extract external ID field conversion into _convert_external_id_field helper\n- Extract external ID field processing into _process_external_id_fields helper\n- Extract error handling into _handle_create_error helper\n- Fix line length violations by breaking long lines\n- Add proper type annotations\n- Fix trailing whitespace issues\n\nAll pre-commit checks and tests now pass.
1 parent a7db31a commit 801fb94

File tree

1 file changed

+148
-77
lines changed

1 file changed

+148
-77
lines changed

src/odoo_data_flow/import_threaded.py

Lines changed: 148 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,145 @@ def __init__(
330330
)
331331

332332

333-
def _create_batch_individually( # noqa: C901
333+
def _convert_external_id_field(
334+
model: Any,
335+
field_name: str,
336+
field_value: str,
337+
converted_vals: dict[str, Any],
338+
) -> list[str]:
339+
"""Convert an external ID field to a database ID.
340+
341+
Args:
342+
model: The Odoo model object
343+
field_name: The field name (e.g., 'parent_id/id')
344+
field_value: The external ID value
345+
converted_vals: Dictionary to store converted values
346+
347+
Returns:
348+
List of external ID field names that were processed
349+
"""
350+
external_id_fields = [field_name]
351+
base_field_name = field_name[:-3] # Remove '/id' suffix
352+
353+
# Handle empty external ID references
354+
if not field_value:
355+
# Empty external ID means no value for this field
356+
converted_vals[base_field_name] = False
357+
log.debug(
358+
f"Converted empty external ID {field_name} -> {base_field_name} (False)"
359+
)
360+
else:
361+
# Convert external ID to database ID
362+
try:
363+
# Look up the database ID for this external ID
364+
record_ref = model.browse().env.ref(field_value, raise_if_not_found=False)
365+
if record_ref:
366+
converted_vals[base_field_name] = record_ref.id
367+
log.debug(
368+
f"Converted external ID {field_name} ({field_value}) -> "
369+
f"{base_field_name} ({record_ref.id})"
370+
)
371+
else:
372+
# If we can't find the external ID, set to False
373+
converted_vals[base_field_name] = False
374+
log.warning(
375+
f"Could not find record for external ID '{field_value}', "
376+
f"setting {base_field_name} to False"
377+
)
378+
except Exception as e:
379+
log.warning(
380+
f"Error looking up external ID '{field_value}' for field "
381+
f"'{field_name}': {e}"
382+
)
383+
# On error, set to False
384+
converted_vals[base_field_name] = False
385+
386+
return external_id_fields
387+
388+
389+
def _process_external_id_fields(
390+
model: Any,
391+
clean_vals: dict[str, Any],
392+
) -> tuple[dict[str, Any], list[str]]:
393+
"""Process all external ID fields in the clean values.
394+
395+
Args:
396+
model: The Odoo model object
397+
clean_vals: Dictionary of clean field values
398+
399+
Returns:
400+
Tuple of (converted_vals, external_id_fields)
401+
"""
402+
converted_vals: dict[str, Any] = {}
403+
external_id_fields: list[str] = []
404+
405+
for field_name, field_value in clean_vals.items():
406+
# Handle external ID references (e.g., 'parent_id/id' -> 'parent_id')
407+
if field_name.endswith("/id"):
408+
fields = _convert_external_id_field(
409+
model, field_name, field_value, converted_vals
410+
)
411+
external_id_fields.extend(fields)
412+
else:
413+
# Regular field - pass through as-is
414+
converted_vals[field_name] = field_value
415+
416+
return converted_vals, external_id_fields
417+
418+
419+
def _handle_create_error(
420+
i: int,
421+
create_error: Exception,
422+
line: list[Any],
423+
error_summary: str,
424+
) -> tuple[str, list[list[Any]], str]:
425+
"""Handle errors during record creation.
426+
427+
Args:
428+
i: The row index
429+
create_error: The exception that occurred
430+
line: The data line being processed
431+
error_summary: Current error summary
432+
433+
Returns:
434+
Tuple of (error_message, failed_lines, error_summary)
435+
"""
436+
failed_lines = []
437+
438+
# Handle JSON-RPC exceptions that contain IndexErrors
439+
error_str = str(create_error).lower()
440+
441+
# Check if this is a JSON-RPC exception containing an IndexError/tuple index error
442+
if "tuple index out of range" in error_str or "indexerror" in error_str:
443+
error_message = f"Tuple unpacking error in row {i + 1}: {create_error}"
444+
failed_line = [*list(line), error_message]
445+
failed_lines.append(failed_line)
446+
if "Fell back to create" in error_summary:
447+
error_summary = "Tuple unpacking error detected"
448+
return error_message, failed_lines, error_summary
449+
450+
# Handle other specific error types
451+
error_message = str(create_error).replace("\n", " | ")
452+
453+
# Handle "tuple index out of range" errors specifically
454+
if "tuple index out of range" in error_message:
455+
error_message = f"Tuple unpacking error in row {i + 1}: {error_message}"
456+
457+
# Handle invalid field errors (the new issue we discovered)
458+
elif "Invalid field" in error_message and "/id" in error_message:
459+
error_message = (
460+
f"Invalid external ID field detected in row {i + 1}: {error_message}"
461+
)
462+
463+
failed_line = [*list(line), error_message]
464+
failed_lines.append(failed_line)
465+
if "Fell back to create" in error_summary:
466+
error_summary = error_message
467+
468+
return error_message, failed_lines, error_summary
469+
470+
471+
def _create_batch_individually(
334472
model: Any,
335473
batch_lines: list[list[Any]],
336474
batch_header: list[str],
@@ -367,59 +505,15 @@ def _create_batch_individually( # noqa: C901
367505
clean_vals = {
368506
k: v
369507
for k, v in vals.items()
370-
if k.split("/")[0]
371-
not in ignore_set # Allow external ID fields through for conversion
508+
if k.split("/")[0] not in ignore_set
509+
# Allow external ID fields through for conversion
372510
}
373511

374512
# 3. CREATE
375513
# Convert external ID references to actual database IDs before creating
376-
converted_vals = {}
377-
external_id_fields = []
378-
for field_name, field_value in clean_vals.items():
379-
# Handle external ID references (e.g., 'parent_id/id' -> 'parent_id')
380-
if field_name.endswith("/id"):
381-
external_id_fields.append(field_name)
382-
base_field_name = field_name[:-3] # Remove '/id' suffix
383-
# Handle empty external ID references
384-
if not field_value:
385-
# Empty external ID means no value for this field
386-
converted_vals[base_field_name] = False
387-
log.debug(
388-
f"Converted empty external ID {field_name} -> "
389-
f"{base_field_name} (False)"
390-
)
391-
else:
392-
# Convert external ID to database ID
393-
try:
394-
# Look up the database ID for this external ID
395-
record_ref = model.browse().env.ref(
396-
field_value, raise_if_not_found=False
397-
)
398-
if record_ref:
399-
converted_vals[base_field_name] = record_ref.id
400-
log.debug(
401-
f"Converted external ID {field_name} "
402-
f"({field_value}) -> {base_field_name} "
403-
f"({record_ref.id})"
404-
)
405-
else:
406-
# If we can't find the external ID, set to False
407-
converted_vals[base_field_name] = False
408-
log.warning(
409-
f"Could not find record for external ID "
410-
f"'{field_value}' setting {base_field_name}"
411-
f" to False"
412-
)
413-
except Exception as e:
414-
log.warning(
415-
f"Error looking up external ID '{field_value}' "
416-
f"for field '{field_name}': {e}"
417-
)
418-
# On error, set to False
419-
converted_vals[base_field_name] = False
420-
else:
421-
# Regular field - pass through as-is
422-
converted_vals[field_name] = field_value
514+
converted_vals, external_id_fields = _process_external_id_fields(
515+
model, clean_vals
516+
)
423517

424518
log.debug(f"External ID fields found: {external_id_fields}")
425519
log.debug(f"Converted vals keys: {list(converted_vals.keys())}")
@@ -433,33 +527,10 @@ def _create_batch_individually( # noqa: C901
433527
error_summary = "Malformed CSV row detected"
434528
continue
435529
except Exception as create_error:
436-
# Handle JSON-RPC exceptions that contain IndexErrors
437-
error_str = str(create_error).lower()
438-
439-
# Check if this is a JSON-RPC exception containing an
440-
# IndexError/tuple index error
441-
if "tuple index out of range" in error_str or "indexerror" in error_str:
442-
error_message = f"Tuple unpacking error in row {i + 1}: {create_error}"
443-
failed_line = [*list(line), error_message]
444-
failed_lines.append(failed_line)
445-
if "Fell back to create" in error_summary:
446-
error_summary = "Tuple unpacking error detected"
447-
continue
448-
449-
# Handle other specific error types
450-
error_message = str(create_error).replace("\n", " | ")
451-
452-
# Handle "tuple index out of range" errors specifically
453-
if "tuple index out of range" in error_message:
454-
error_message = f"Tuple unpacking error in row {i + 1}: {error_message}"
455-
456-
# Handle invalid field errors (the new issue we discovered)
457-
elif "Invalid field" in error_message and "/id" in error_message:
458-
error_message = "Invalid external ID field detected in row "
459-
f"{i + 1}: {error_message}"
460-
461-
failed_line = [*list(line), error_message]
462-
failed_lines.append(failed_line)
530+
error_message, new_failed_lines, error_summary = _handle_create_error(
531+
i, create_error, line, error_summary
532+
)
533+
failed_lines.extend(new_failed_lines)
463534
if "Fell back to create" in error_summary:
464535
error_summary = error_message
465536
return {

0 commit comments

Comments
 (0)