Skip to content

map_object emits spurious "Unexpected" warnings for slots with range=None #172

@matentzn

Description

@matentzn

When a source schema has slots with no explicit range (i.e., range=None), _map_value_by_range recurses into map_object with None as the source type. For scalar values (strings, ints, etc.), map_object then hits the not isinstance(source_obj, dict) check and logs a warning:

WARNING Unexpected: <value> for type None

This happens for every scalar field without an explicit range, producing many spurious warnings during normal transforms.

Root Cause

In _map_value_by_range (object_transformer.py), when source_class_slot.range is None and there are no any_of enums, the method falls through to the recursive map_object call at line 611. For scalar values, map_object receives source_type=None, which isn't in all_types() or all_enums(), the value isn't a dict, so it logs the warning and returns the value unchanged.

The recursion is pointless for scalars when there's no range — there's nothing to map.

Reproducer

from linkml_map.transformer.object_transformer import ObjectTransformer
from linkml_runtime.utils.schemaview import SchemaView
import yaml
import tempfile
import logging

logging.basicConfig(level=logging.DEBUG)


def write_temp(content):
    f = tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False)
    f.write(content) if isinstance(content, str) else yaml.dump(content, f)
    f.close()
    return f.name


# Schema where slots have no explicit range (inherits default_range or None)
source_schema = """
id: https://example.org/source
name: source
prefixes:
  linkml: https://w3id.org/linkml/
imports:
  - linkml:types

classes:
  Record:
    attributes:
      record_id:
        identifier: true
      title:
      description:
"""

target_schema = """
id: https://example.org/target
name: target
prefixes:
  linkml: https://w3id.org/linkml/
imports:
  - linkml:types
default_range: string

classes:
  Output:
    attributes:
      id:
      name:
      description:
"""

transform = {
    "id": "test",
    "class_derivations": {
        "Output": {
            "populated_from": "Record",
            "slot_derivations": {
                "id": {"populated_from": "record_id"},
                "name": {"populated_from": "title"},
                "description": {"populated_from": "description"},
            },
        }
    },
}

src_path = write_temp(source_schema)
tgt_path = write_temp(target_schema)
transform["source_schema"] = src_path
transform["target_schema"] = tgt_path
tr_path = write_temp(transform)

tr = ObjectTransformer(unrestricted_eval=True)
tr.source_schemaview = SchemaView(src_path)
tr.load_transformer_specification(tr_path)

input_obj = {"record_id": "rec-001", "title": "Hello", "description": "A test record"}
tr.index(input_obj, "Record")
result = tr.map_object(input_obj, "Record")
# Produces 3 "Unexpected" warnings, one per field

Fix

In _map_value_by_range, when range is None/"Any" and there are no any_of enums, return scalar values directly instead of recursing:

if not isinstance(v, (dict, list)):
    return v

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions