Skip to content
Open
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) | | 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) | | direct |
| [copy_directives](copy_directives.md) | * <br/> [CopyDirective](CopyDirective.md) | | [ElementDerivation](ElementDerivation.md) |
Expand Down Expand Up @@ -257,19 +257,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 @@ -327,7 +331,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 @@ -336,11 +342,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 @@ -236,27 +236,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, 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 @@ -486,15 +486,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 @@ -1164,9 +1164,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