Allow multiple class_derivations for the same target class#118
Allow multiple class_derivations for the same target class#118amc-corey-cox merged 7 commits intomainfrom
Conversation
Add inlined_as_list: true to TransformationSpecification.class_derivations, changing storage from Dict[str, ClassDerivation] to List[ClassDerivation]. This enables multiple derivations targeting the same class name (e.g., two source tables both mapping to Condition), which was previously impossible due to dict key uniqueness. Backward compatibility is preserved via a Pydantic field_validator that auto-converts dict input to list format, so all existing YAML specs and programmatic construction continue to work unchanged. Closes #111 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per EXTRACT marker in map_object(), extract source type resolution logic to a private method. Add comprehensive unit tests covering all resolution paths (explicit, no-schema fallback, tree_root, errors) and an integration test through the full transformer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The hand-added field_validator was lost when CI regenerates the model. Add a class.py.jinja template override that injects the coerce_class_derivations validator for TransformationSpecification, so it survives model regeneration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR enables class_derivations to support multiple derivations for the same target class name by changing it from a Dict[str, ClassDerivation] to a List[ClassDerivation] in TransformationSpecification. This is needed to support use cases like mapping multiple source tables to the same target class (e.g., two source tables both mapping to Condition).
Changes:
- Modified schema to use
inlined_as_list: trueforTransformationSpecification.class_derivationswhile keepingObjectDerivation.class_derivationsas a dict - Added backward compatibility via Pydantic
field_validatorto accept dict input and convert to list - Updated all code to iterate over the list instead of using dict methods (
.values(),.items(), etc.) - Added comprehensive test coverage for all input formats and edge cases
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/linkml_map/datamodel/transformer_model.yaml | Added inlined_as_list: true to class_derivations |
| src/linkml_map/datamodel/transformer_model.py | Generated Pydantic model with List type and field_validator |
| templates/pydantic/class.py.jinja | Added field_validator template for backward compatibility |
| src/linkml_map/transformer/transformer.py | Added preprocessing and normalization logic, updated helper methods |
| src/linkml_map/transformer/object_transformer.py | Updated to use list indexing, added _resolve_source_type method |
| src/linkml_map/utils/loaders.py | Added preprocessing call |
| src/linkml_map/session.py | Added preprocessing call |
| src/linkml_map/inference/*.py | Updated to iterate over list |
| src/linkml_map/compiler/*.py | Updated to iterate over list |
| tests/test_datamodel.py | Added 11 comprehensive tests |
| tests/test_transformer/*.py | Updated existing tests and added new ones |
| tests/test_schema_mapper/*.py | Updated to use .append() instead of dict assignment |
PR #118 Issue:
|
| # | Severity | Issue |
|---|---|---|
| 1 | Critical | schema_mapper.derive_schema silently drops first derivation with duplicate names |
| 2 | Important | session.py/loaders.py skip ObjectDerivation dict conversion |
| 3 | Important | _get_class_derivation fails with duplicate names when populated_from not set |
| 4 | Minor | _find_class_derivation_by_name first-match behavior undocumented |
| 5 | Minor | test_self_transform duplicates preprocessing logic (DRY) |
| 6 | Minor | get_biolink_class_derivations return type annotation is wrong |
| 7 | Minor | Backward-compat validator ignores dict key for ClassDerivation objects |
| 8 | Suggestion | Compact-list heuristic needs an explanatory comment |
…te derivations Consolidate the duplicated preprocess→normalize→create-spec pipeline into a single `_normalize_spec_dict` classmethod so all four entry points (including Session and loaders) get nested ObjectDerivation fixup. Add merge-on-conflict logic in `derive_schema` so duplicate-name class derivations combine their slots instead of silently dropping the first derivation's attributes. Include minor fixes: docstring/type-annotation corrections, compact-list heuristic comments, and `coerce_class_derivations` handling of ClassDerivation objects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the thorough review, Kevin. All 8 issues are addressed in 14788f6: Issue #1 (Critical) — Issue #2 — Consolidated all four normalization entry points into Issue #3 — Added Issue #4 — Updated Issue #5 — Replaced the inline None-value fix in Issue #6 — Changed return type from Issue #7 — Added Issue #8 — Added comments explaining the compact-list heuristic in |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… has none Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
inlined_as_list: truetoTransformationSpecification.class_derivationsin the schema, changing storage fromDict[str, ClassDerivation]toList[ClassDerivation]Condition), which was previously impossible due to dict key uniquenessclass_derivations={"Agent": ...}) continue to work — a Pydanticfield_validatorauto-converts dicts to listsChanges
inlined_as_list: trueonclass_derivations; regenerated Pydantic model; hand-added backward-compatfield_validatorNonevalues and compact list keys beforeReferenceValidator.normalize(); convertObjectDerivation.class_derivationsback to dict after normalization (it stays as dict).values()→ direct iteration,.items()→for cd in ...: cd.name,[key] =→.append()across transformer, inference, compiler, and test modulescreate_transformer_specificationCloses #111
Test plan
uv run pytest tests/test_datamodel.py -v— 11 tests covering all input/output formatsuv run pytest tests/ -q— 258 passed, 4 skipped, 0 failuresuv run ruff checkon modified files — no new lint issues🤖 Generated with Claude Code