Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/bioetl/domain/ports/workflow_foreign_key_reconciliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class ForeignKeyReconciliationRequest:
nulls_equal: bool = False

def __post_init__(self) -> None:
self._validate_required_fields()
self._validate_composite_keys()

def _validate_required_fields(self) -> None:
if not self.source_table.strip():
raise ValueError("source_table cannot be empty")
if not self.reference_table.strip():
Expand All @@ -37,24 +41,20 @@ def __post_init__(self) -> None:
raise ValueError("reference_key cannot be empty")
if not self.primary_keys:
raise ValueError("primary_keys cannot be empty")

def _validate_composite_keys(self) -> None:
if self.source_keys is None and self.reference_keys is None:
return
if self.source_keys is None or self.reference_keys is None:
raise ValueError(
"source_keys and reference_keys must be provided together"
)
raise ValueError("source_keys and reference_keys must be provided together")
if not self.source_keys or not self.reference_keys:
raise ValueError("source_keys and reference_keys cannot be empty")
if len(self.source_keys) != len(self.reference_keys):
raise ValueError(
"source_keys and reference_keys must have the same length"
)
raise ValueError("source_keys and reference_keys must have the same length")
if self.source_keys[0].strip() != self.source_key.strip():
raise ValueError("source_key must match the first source_keys entry")
if self.reference_keys[0].strip() != self.reference_key.strip():
raise ValueError(
"reference_key must match the first reference_keys entry"
)
raise ValueError("reference_key must match the first reference_keys entry")

@property
def effective_source_keys(self) -> tuple[str, ...]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _build_detached_backend_env(
current_env: dict[str, str] | None = None,
) -> dict[str, str]:
"""Ensure detached backend subprocess can import the src-layout package."""
env = dict(current_env if current_env is not None else getattr(os, "environ"))
env = dict(current_env if current_env is not None else os.environ)
src_root = Path(__file__).resolve().parents[6]
existing_pythonpath = env.get("PYTHONPATH", "").strip()
pythonpath_parts = [str(src_root)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from collections import OrderedDict

from bioetl.infrastructure.adapters.common.response_shapes import (
extract_response_items,
extract_response_mapping,
Expand Down Expand Up @@ -40,3 +42,11 @@ def test_extract_response_text_returns_only_string_values() -> None:
assert extract_response_text({"nextCursor": "cursor-1"}, "nextCursor") == "cursor-1"
assert extract_response_text({"nextCursor": 10}, "nextCursor") is None
assert extract_response_text({}, "nextCursor") is None


def test_extract_response_mapping_handles_various_edge_cases() -> None:
assert extract_response_mapping({"meta": None}, "meta") is None
assert extract_response_mapping({"meta": "string_not_mapping"}, "meta") is None

ordered_map = OrderedDict([("a", 1), ("b", 2)])
assert extract_response_mapping({"meta": ordered_map}, "meta") == ordered_map
Loading