Skip to content

Field overrides in subclasses lose range metadata, breaking IRI handling #85

@simontaurus

Description

@simontaurus

Description

When a subclass overrides a field that has range metadata (used by LinkedBaseModel.__init__ to handle IRI strings), the range information from the parent's Field() is lost. This causes:

  1. IRI strings passed to the field raise ValidationError: value is not a valid dict
  2. Object refs don't get their IRI extracted to __iris__

Root cause

In LinkedBaseModel.__init__, IRI handling checks field.field_info.extra for a "range" key. When a subclass overrides a field with a plain annotation (e.g. tool: list[Child] | None = None), pydantic v1 creates a new FieldInfo without the parent's extra kwargs, so "range" is absent.

Minimal reproduction

from pydantic.v1 import Field
from oold.model.v1 import LinkedBaseModel
from oold.backend.document_store import SimpleDictDocumentStore
from oold.backend.interface import set_backend, SetBackendParam

backend = SimpleDictDocumentStore()
set_backend(SetBackendParam(iri="Item", backend=backend))

class Parent(LinkedBaseModel):
    class Config:
        schema_extra = {"uuid": "00000000-0000-0000-0000-000000000001"}
    type: list[str] | None = ["Category:OSW00000000000000000000000000000001"]
    id: str = "Item:OSW00000000000000000000000000000001"
    name: str = "parent"

class ProcessBase(LinkedBaseModel):
    class Config:
        schema_extra = {"uuid": "00000000-0000-0000-0000-000000000010"}
    tool: list[Parent] | None = Field(None, range="Category:Parent")

class GoodSubclass(ProcessBase):
    """Inherits tool with range metadata — works"""
    class Config:
        schema_extra = {"uuid": "00000000-0000-0000-0000-000000000011"}

class BadSubclass(ProcessBase):
    """Overrides tool, loses range metadata — breaks"""
    class Config:
        schema_extra = {"uuid": "00000000-0000-0000-0000-000000000012"}
    tool: list[Parent] | None = None

parent = Parent(name="test_parent")
parent_iri = parent.get_iri()
backend._store[parent_iri] = parent.dict()

# GoodSubclass: IRI resolved correctly
good = GoodSubclass(tool=[parent_iri])
print(good.__iris__.get("tool"))   # ['Item:OSW...'] ✓
print(good.tool[0].name)           # 'test_parent' ✓

# BadSubclass: ValidationError
bad = BadSubclass(tool=[parent_iri])  # ✗ raises ValidationError

Output:

['Item:OSW00000000000000000000000000000001']
test_parent

ValidationError: 1 validation error for BadSubclass
tool -> 0
  value is not a valid dict (type=type_error.dict)

Workaround

Re-declare the field with explicit range in the subclass:

class FixedSubclass(ProcessBase):
    tool: list[Parent] | None = Field(None, range="Category:Parent")

Suggestion

LinkedBaseModel.__init__ could fall back to checking parent class field extras when range is not found in the overriding field. Alternatively, the metaclass could propagate range metadata to subclass field overrides automatically.

Environment

  • oold: 0.16.2
  • pydantic: v1 compat mode (pydantic 2.x with pydantic.v1)
  • Python: 3.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions