Skip to content
Merged
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
22 changes: 15 additions & 7 deletions docs/schema/PermissibleValueDerivation.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ URI: [linkmlmap:PermissibleValueDerivation](https://w3id.org/linkml/transformer/
| --- | --- | --- | --- |
| [name](name.md) | 1 <br/> [String](String.md) | Target permissible value text | direct |
| [expr](expr.md) | 0..1 <br/> [String](String.md) | An expression evaluated on the source object to derive this permissible value | direct |
| [populated_from](populated_from.md) | 0..1 <br/> [String](String.md) | Source permissible value that maps to this target permissible value | direct |
| [populated_from](populated_from.md) | * <br/> [String](String.md) | Source permissible value(s) that map to this target permissible value | direct |
| [sources](sources.md) | * <br/> [String](String.md) | Deprecated | direct |
| [hide](hide.md) | 0..1 <br/> [Boolean](Boolean.md) | True if this is suppressed | direct |
| [copy_directives](copy_directives.md) | * <br/> [CopyDirective](CopyDirective.md) | Directives controlling which sub-elements of the source element are copied in... | [ElementDerivation](ElementDerivation.md) |
Expand Down Expand Up @@ -259,19 +259,23 @@ attributes:
range: string
populated_from:
name: populated_from
description: Source permissible value that maps to this target permissible value.
description: Source permissible value(s) that map to this target permissible value.
Accepts a single value or a list; scalar input is normalized to a one-element
list at load time.
from_schema: https://w3id.org/linkml/transformer
domain_of:
- ClassDerivation
- SlotDerivation
- EnumDerivation
- PermissibleValueDerivation
range: string
multivalued: true
sources:
name: sources
description: Deprecated. Use populated_from instead.
deprecated: Deprecated. Use populated_from instead. See https://github.com/linkml/linkml-map/issues/193
for planned list support in populated_from. Will be removed in a future version.
deprecated: Deprecated. Use populated_from instead, which now accepts a list.
See https://github.com/linkml/linkml-map/issues/193. Will be removed in a future
version.
from_schema: https://w3id.org/linkml/transformer
domain_of:
- ClassDerivation
Expand Down Expand Up @@ -332,7 +336,9 @@ attributes:
range: string
populated_from:
name: populated_from
description: Source permissible value that maps to this target permissible value.
description: Source permissible value(s) that map to this target permissible value.
Accepts a single value or a list; scalar input is normalized to a one-element
list at load time.
from_schema: https://w3id.org/linkml/transformer
owner: PermissibleValueDerivation
domain_of:
Expand All @@ -341,11 +347,13 @@ attributes:
- EnumDerivation
- PermissibleValueDerivation
range: string
multivalued: true
sources:
name: sources
description: Deprecated. Use populated_from instead.
deprecated: Deprecated. Use populated_from instead. See https://github.com/linkml/linkml-map/issues/193
for planned list support in populated_from. Will be removed in a future version.
deprecated: Deprecated. Use populated_from instead, which now accepts a list.
See https://github.com/linkml/linkml-map/issues/193. Will be removed in a future
version.
from_schema: https://w3id.org/linkml/transformer
owner: PermissibleValueDerivation
domain_of:
Expand Down
28 changes: 14 additions & 14 deletions src/linkml_map/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,27 +254,27 @@ def _pre_flight_validate(
) -> None:
"""Surface static spec checks before performing a CLI action.

Runs deprecation checks and reference resolution against the loaded
specification, printing all findings to stderr. **Does not gate** —
even error-severity findings are informational only; the runtime
remains the authoritative arbiter of whether a transformation can
actually execute. Users who want fail-fast behavior should run
``validate-spec --strict`` separately.

Validates the merged ``tr.specification`` (after multi-file loading
is complete) so cross-file issues surface where users would see
them via ``validate-spec --merge``. Reuses ``tr.source_schemaview``
and ``tr.target_schemaview`` when set, avoiding a duplicate load
of large or remote schemas.
Replays pre-normalize scan messages captured at spec-load time and runs
reference resolution, printing all findings to stderr. **Does not gate** —
findings are informational only; the runtime remains the authoritative
arbiter of whether a transformation can actually execute. Users who want
fail-fast behavior should run ``validate-spec --strict`` separately.

Reading scan messages from ``tr.spec_messages`` rather than re-scanning
the post-migration spec means deprecations whose source fields were
cleared by normalization (e.g., ``object_derivations``, PV ``sources``)
are still surfaced here. Reuses ``tr.source_schemaview`` and
``tr.target_schemaview`` when set, avoiding a duplicate load of large
or remote schemas.
"""
from linkml_map.validator import check_deprecated_fields, validate_spec_semantics
from linkml_map.validator import validate_spec_semantics

if tr.specification is None:
return

spec_dict = tr.specification.model_dump(exclude_none=True)

messages = list(check_deprecated_fields(spec_dict))
messages = list(tr.spec_messages)
messages.extend(
validate_spec_semantics(
spec_dict,
Expand Down
2 changes: 1 addition & 1 deletion src/linkml_map/compiler/templates/markdown.j2
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
| Target | Source | Info |
| ------ | ------ | ---- |
{%- for pvd in ed.permissible_value_derivations.values() %}
| {{ pvd.name }} | {{ pvd.populated_from }} | . |
| {{ pvd.name }} | {{ (pvd.populated_from or []) | join(", ") }} | . |
{%- endfor -%}
{% endfor %}
9 changes: 4 additions & 5 deletions src/linkml_map/datamodel/transformer_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,13 @@ class PermissibleValueDerivation(ElementDerivation):
expr: Optional[str] = Field(default=None, description="""An expression evaluated on the source object to derive this permissible value. Should be specified using the LinkML expression language.""", json_schema_extra = { "linkml_meta": {'domain_of': ['SlotDerivation',
'EnumDerivation',
'PermissibleValueDerivation']} })
populated_from: Optional[str] = Field(default=None, description="""Source permissible value that maps to this target permissible value.""", json_schema_extra = { "linkml_meta": {'domain_of': ['ClassDerivation',
populated_from: Optional[list[str]] = Field(default_factory=list, description="""Source permissible value(s) that map to this target permissible value. Accepts a single value or a list; scalar input is normalized to a one-element list at load time.""", json_schema_extra = { "linkml_meta": {'domain_of': ['ClassDerivation',
'SlotDerivation',
'EnumDerivation',
'PermissibleValueDerivation']} })
sources: Optional[list[str]] = Field(default_factory=list, description="""Deprecated. Use populated_from instead.""", json_schema_extra = { "linkml_meta": {'deprecated': 'Deprecated. Use populated_from instead. See '
'https://github.com/linkml/linkml-map/issues/193 for planned '
'list support in populated_from. Will be removed in a future '
'version.',
sources: Optional[list[str]] = Field(default_factory=list, description="""Deprecated. Use populated_from instead.""", json_schema_extra = { "linkml_meta": {'deprecated': 'Deprecated. Use populated_from instead, which now accepts a '
'list. See https://github.com/linkml/linkml-map/issues/193. '
'Will be removed in a future version.',
'domain_of': ['ClassDerivation',
'SlotDerivation',
'EnumDerivation',
Expand Down
11 changes: 7 additions & 4 deletions src/linkml_map/datamodel/transformer_model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -532,15 +532,18 @@ classes:
range: string
populated_from:
range: string
multivalued: true
description: >-
Source permissible value that maps to this target permissible value.
Source permissible value(s) that map to this target permissible value.
Accepts a single value or a list; scalar input is normalized to a
one-element list at load time.
sources:
range: string
multivalued: true
deprecated: >-
Deprecated. Use populated_from instead.
See https://github.com/linkml/linkml-map/issues/193 for planned
list support in populated_from. Will be removed in a future version.
Deprecated. Use populated_from instead, which now accepts a list.
See https://github.com/linkml/linkml-map/issues/193. Will be removed
in a future version.
description: >-
Deprecated. Use populated_from instead.
hide:
Expand Down
23 changes: 0 additions & 23 deletions src/linkml_map/inference/inference.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
"""Infer missing values in a specification."""

import warnings

from linkml_runtime import SchemaView

from linkml_map.datamodel.transformer_model import TransformationSpecification
from linkml_map.utils.fk_utils import resolve_fk_path


def _warn_deprecated_fields(specification: TransformationSpecification) -> None:
"""Emit ``DeprecationWarning`` for any deprecated field usage.

Thin shim over ``validator._check_deprecated_fields`` that re-emits
deprecation-category messages as ``DeprecationWarning`` so runtime
callers (and any code with Python ``warnings`` filters configured)
keep getting the same signal they always did.

The same underlying check is consumed by static validation paths
(``validate-spec`` and pre-flight from other CLI commands) via
``ValidationMessage`` records — see ``validator._check_deprecated_fields``.
"""
from linkml_map.validator import check_deprecated_fields

spec_dict = specification.model_dump(exclude_none=True)
for msg in check_deprecated_fields(spec_dict):
if msg.category == "deprecated":
warnings.warn(msg.message, DeprecationWarning, stacklevel=3)


def induce_missing_values(specification: TransformationSpecification, source_schemaview: SchemaView) -> None:
"""
Infer missing values in a specification.
Expand All @@ -41,7 +19,6 @@ def induce_missing_values(specification: TransformationSpecification, source_sch
for cd in specification.class_derivations:
if not cd.populated_from:
cd.populated_from = cd.name
_warn_deprecated_fields(specification)

for cd in specification.class_derivations:
for sd in cd.slot_derivations.values():
Expand Down
13 changes: 11 additions & 2 deletions src/linkml_map/inference/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,18 @@ def invert_enum_derivation(self, ed: EnumDerivation, spec: TransformationSpecifi
msg = "TODO: invert enum derivation with expression"
raise NonInvertibleSpecificationError(msg)
for pv_deriv in ed.permissible_value_derivations.values():
sources = pv_deriv.populated_from or [pv_deriv.name]
if len(sources) > 1:
msg = (
f"Cannot invert PermissibleValueDerivation '{pv_deriv.name}': "
f"populated_from has multiple source values {sources}, "
f"which is not a one-to-one mapping."
)
raise NonInvertibleSpecificationError(msg)
inverted_name = sources[0]
inverted_pv_deriv = PermissibleValueDerivation(
name=pv_deriv.populated_from if pv_deriv.populated_from else pv_deriv.name,
populated_from=pv_deriv.name,
name=inverted_name,
populated_from=[pv_deriv.name],
)
inverted_ed.permissible_value_derivations[inverted_pv_deriv.name] = inverted_pv_deriv
return inverted_ed
Expand Down
13 changes: 5 additions & 8 deletions src/linkml_map/inference/schema_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,13 @@ def _derive_enum(self, enum_derivation: EnumDerivation) -> EnumDefinition:
target_enum.attributes = {}
target_enum.slot_usage = {}
for pv_derivation in enum_derivation.permissible_value_derivations.values():
if pv_derivation.populated_from:
pv = PermissibleValue(text=pv_derivation.populated_from)
target_enum.permissible_values[pv.text] = pv
elif pv_derivation.sources:
for source in pv_derivation.sources:
pv = PermissibleValue(text=source)
target_enum.permissible_values[pv.text] = pv
else:
sources = pv_derivation.populated_from or pv_derivation.sources
if not sources:
msg = f"Missing populated_from or sources for {pv_derivation}"
raise ValueError(msg)
for source in sources:
pv = PermissibleValue(text=source)
target_enum.permissible_values[pv.text] = pv
if enum_derivation.mirror_source:
for pv in source_enum.permissible_values.values():
if pv.text not in target_enum.permissible_values:
Expand Down
12 changes: 12 additions & 0 deletions src/linkml_map/transformer/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
from typing import Any


class SpecificationError(ValueError):
"""The transformation specification is malformed or internally inconsistent.

Raised during spec loading when the pre-normalize scan detects a
user-intent error that the runtime cannot disambiguate (e.g., setting
both ``populated_from`` and ``sources`` on the same derivation, or
setting both ``object_derivations`` and ``class_derivations`` on a
slot). Subclasses ``ValueError`` for backward compatibility with
callers that already catch ``ValueError`` from ``_normalize_spec_dict``.
"""


@dataclass
class TransformationError(Exception):
"""A row-level error during data transformation.
Expand Down
13 changes: 10 additions & 3 deletions src/linkml_map/transformer/object_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1174,9 +1174,16 @@ def transform_enum(self, source_value: str, enum_names: list[str], source_obj: A
if v is not None:
return v
for pv_deriv in enum_deriv.permissible_value_derivations.values():
if source_value == pv_deriv.populated_from:
return pv_deriv.name
if source_value in pv_deriv.sources:
if pv_deriv.sources:
from linkml_map.transformer.errors import SpecificationError

msg = (
f"PermissibleValueDerivation '{pv_deriv.name}' has 'sources' set; "
"this should have been migrated to 'populated_from' during spec load. "
"Did the spec bypass Transformer._normalize_spec_dict?"
)
raise SpecificationError(msg)
if pv_deriv.populated_from and source_value in pv_deriv.populated_from:
Comment thread
amc-corey-cox marked this conversation as resolved.
return pv_deriv.name
if enum_deriv.mirror_source:
return str(source_value)
Expand Down
Loading
Loading