Skip to content

Commit 8c023a7

Browse files
bosdbosd
authored andcommitted
fix: keyerror: relation_table
1 parent 6211968 commit 8c023a7

File tree

3 files changed

+96
-16
lines changed

3 files changed

+96
-16
lines changed

src/odoo_data_flow/lib/preflight.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -319,19 +319,31 @@ def _plan_deferrals_and_strategies(
319319
.collect()
320320
.item()
321321
)
322-
if relation_count >= 500:
323-
strategies[clean_field_name] = {
324-
"strategy": "direct_relational_import",
325-
"relation_table": field_info["relation_table"],
326-
"relation_field": field_info["relation_field"],
327-
"relation": field_info["relation"],
328-
}
322+
# Check if required keys exist for many2many fields
323+
relation_table = field_info.get("relation_table")
324+
relation_field = field_info.get("relation_field")
325+
relation = field_info.get("relation")
326+
327+
if relation_table and relation_field:
328+
if relation_count >= 500:
329+
strategies[clean_field_name] = {
330+
"strategy": "direct_relational_import",
331+
"relation_table": relation_table,
332+
"relation_field": relation_field,
333+
"relation": relation,
334+
}
335+
else:
336+
strategies[clean_field_name] = {
337+
"strategy": "write_tuple",
338+
"relation_table": relation_table,
339+
"relation_field": relation_field,
340+
"relation": relation,
341+
}
329342
else:
343+
# Fallback strategy when relation information is incomplete
330344
strategies[clean_field_name] = {
331345
"strategy": "write_tuple",
332-
"relation_table": field_info["relation_table"],
333-
"relation_field": field_info["relation_field"],
334-
"relation": field_info["relation"],
346+
"relation": relation,
335347
}
336348
elif is_o2m:
337349
deferrable_fields.append(clean_field_name)

src/odoo_data_flow/lib/relational_import.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,30 @@ def run_direct_relational_import(
107107
description=f"Pass 2/2: Updating relations for [bold]{field}[/bold]",
108108
)
109109
log.info(f"Running 'Direct Relational Import' for field '{field}'...")
110-
relational_table = strategy_details["relation_table"]
111-
owning_model_fk = strategy_details["relation_field"]
112-
related_model_fk = strategy_details["relation"]
110+
111+
# Check if required keys exist
112+
relational_table = strategy_details.get("relation_table")
113+
owning_model_fk = strategy_details.get("relation_field")
114+
related_model_fk = strategy_details.get("relation")
115+
116+
# If we don't have the required information, we can't proceed with this strategy
117+
if not relational_table or not owning_model_fk:
118+
log.error(
119+
f"Cannot run direct relational import for field '{field}': "
120+
f"Missing relation_table or relation_field in strategy details."
121+
)
122+
return None
113123

114124
# 1. Prepare the owning model's IDs
115125
owning_df = pl.DataFrame({"external_id": id_map.keys(), "db_id": id_map.values()})
116126

117127
# 2. Prepare the related model's IDs using the resolver
118128
all_related_ext_ids = source_df.get_column(field).str.split(",").explode()
129+
if related_model_fk is None:
130+
log.error(
131+
f"Cannot resolve related IDs: Missing relation in strategy details for field '{field}'."
132+
)
133+
return None
119134
related_model_df = _resolve_related_ids(
120135
config, related_model_fk, all_related_ext_ids
121136
)
@@ -170,15 +185,30 @@ def run_write_tuple_import(
170185
description=f"Pass 2/2: Updating relations for [bold]{field}[/bold]",
171186
)
172187
log.info(f"Running 'Write Tuple' for field '{field}'...")
173-
relational_table = strategy_details["relation_table"]
174-
owning_model_fk = strategy_details["relation_field"]
175-
related_model_fk = strategy_details["relation"]
188+
189+
# Check if required keys exist
190+
relational_table = strategy_details.get("relation_table")
191+
owning_model_fk = strategy_details.get("relation_field")
192+
related_model_fk = strategy_details.get("relation")
193+
194+
# If we don't have the required information, we can't proceed with this strategy
195+
if not relational_table or not owning_model_fk:
196+
log.error(
197+
f"Cannot run write tuple import for field '{field}': "
198+
f"Missing relation_table or relation_field in strategy details."
199+
)
200+
return False
176201

177202
# 1. Prepare the owning model's IDs
178203
owning_df = pl.DataFrame({"external_id": id_map.keys(), "db_id": id_map.values()})
179204

180205
# 2. Prepare the related model's IDs using the resolver
181206
all_related_ext_ids = source_df.get_column(field).str.split(",").explode()
207+
if related_model_fk is None:
208+
log.error(
209+
f"Cannot resolve related IDs: Missing relation in strategy details for field '{field}'."
210+
)
211+
return False
182212
related_model_df = _resolve_related_ids(
183213
config, related_model_fk, all_related_ext_ids
184214
)

tests/test_preflight.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,44 @@ def test_direct_relational_import_strategy_for_large_volumes(
398398
== "direct_relational_import"
399399
)
400400

401+
def test_write_tuple_strategy_when_missing_relation_info(
402+
self, mock_polars_read_csv: MagicMock, mock_conf_lib: MagicMock
403+
) -> None:
404+
"""Verify 'write_tuple' is chosen when relation info is missing."""
405+
mock_df_header = MagicMock()
406+
mock_df_header.columns = ["id", "name", "category_id"]
407+
408+
# Setup a more robust mock for the chained Polars calls
409+
mock_df_data = MagicMock()
410+
(
411+
mock_df_data.lazy.return_value.select.return_value.select.return_value.sum.return_value.collect.return_value.item.return_value
412+
) = 100
413+
mock_polars_read_csv.side_effect = [mock_df_header, mock_df_data]
414+
415+
mock_model = mock_conf_lib.return_value.get_model.return_value
416+
mock_model.fields_get.return_value = {
417+
"id": {"type": "integer"},
418+
"name": {"type": "char"},
419+
"category_id": {
420+
"type": "many2many",
421+
"relation": "res.partner.category",
422+
# Missing relation_table and relation_field
423+
},
424+
}
425+
import_plan: dict[str, Any] = {}
426+
result = preflight.deferral_and_strategy_check(
427+
preflight_mode=PreflightMode.NORMAL,
428+
model="res.partner",
429+
filename="file.csv",
430+
config="",
431+
import_plan=import_plan,
432+
)
433+
assert result is True
434+
assert "category_id" in import_plan["deferred_fields"]
435+
assert import_plan["strategies"]["category_id"]["strategy"] == "write_tuple"
436+
# Should not have relation_table or relation_field in strategy
437+
assert "relation" in import_plan["strategies"]["category_id"]
438+
401439
def test_write_tuple_strategy_for_small_volumes(
402440
self, mock_polars_read_csv: MagicMock, mock_conf_lib: MagicMock
403441
) -> None:

0 commit comments

Comments
 (0)