lmignon/extendable-pydantic

Nested models with extendable classes not working

Closed this issue · 3 comments

First, thanks for your work on the extendable libs!

I have tried to do some extendable pydantic schemas with nested model but I have some strange behaviors.

Examples :

from pydantic import BaseModel
from extendable_pydantic import ExtendableModelMeta
from extendable import context, registry
from typing import Optional

class Location(BaseModel, metaclass=ExtendableModelMeta):
    lat: float = 0.1
    lng: float = 10.1

class ExtendedLocation(Location, extends=Location):
    name: str


class Time(BaseModel, metaclass=ExtendableModelMeta):
    hour: int

class Event(BaseModel, metaclass=ExtendableModelMeta):
    location: Location
    time: Optional[Time] = None
    

_registry = registry.ExtendableClassesRegistry()
context.extendable_registry.set(_registry)
_registry.init_registry()

data = {"location": {"lat": 12.3, "lng": 13.2, "name": "My Loc"}}
event = Event(**data)

This code give me the error :

time
  Field required [type=missing, input_value={'location': {'lat': 12.3...13.2, 'name': 'My Loc'}}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.1.2/v/missing

I think it should not, because I did specify a default Value for the nested model Time on Event Class. So this field should be optional and not raise an error.

If I remove the metaclass from my main Event class, the problem does not appear, so this code don't give me any error (which seems correct) :

from pydantic import BaseModel
from extendable_pydantic import ExtendableModelMeta
from extendable import context, registry
from typing import Optional

class Location(BaseModel, metaclass=ExtendableModelMeta):
    lat: float = 0.1
    lng: float = 10.1

class ExtendedLocation(Location, extends=Location):
    name: str


class Time(BaseModel):
    hour: int

class Event(BaseModel):
    location: Location
    time: Optional[Time] = None
    

_registry = registry.ExtendableClassesRegistry()
context.extendable_registry.set(_registry)
_registry.init_registry()

data = {"location": {"lat": 12.3, "lng": 13.2, "name": "My Loc"}}
event = Event(**data)

I quite surprised because the only change is not setting the Event class as extendable, I guess it should not change the validation behavior.

Also, removing the extendable attribute from Event class seems to break the extendability of the Location class.
I mean, the ExtendedLocation class does not seem to extend Location any more, see if we remove the mandatory field name from the data :

from pydantic import BaseModel
from extendable_pydantic import ExtendableModelMeta
from extendable import context, registry
from typing import Optional

class Location(BaseModel, metaclass=ExtendableModelMeta):
    lat: float = 0.1
    lng: float = 10.1

class ExtendedLocation(Location, extends=Location):
    name: str


class Time(BaseModel):
    hour: int

class Event(BaseModel):
    location: Location
    time: Optional[Time] = None
    

_registry = registry.ExtendableClassesRegistry()
context.extendable_registry.set(_registry)
_registry.init_registry()

data = {"location": {"lat": 12.3, "lng": 13.2}}
event = Event(**data)

This should give a missing error of field name from Location class but it does not.

Here are the version of lib used :
extendable==1.2.0
extendable_pydantic==1.1.0
pydantic==2.0.2
python 3.10

@lmignon

@florian-dacosta @hparfr I'll take a look as soon as I've some time. It's strange since I've tests on these specific cases.

@florian-dacosta @hparfr #16 fix your problem.

Regarding your last code snippet

from pydantic import BaseModel
from extendable_pydantic import ExtendableModelMeta
from extendable import context, registry
from typing import Optional

class Location(BaseModel, metaclass=ExtendableModelMeta):
    lat: float = 0.1
    lng: float = 10.1

class ExtendedLocation(Location, extends=Location):
    name: str


class Time(BaseModel):
    hour: int

class Event(BaseModel):
    location: Location
    time: Optional[Time] = None
    

_registry = registry.ExtendableClassesRegistry()
context.extendable_registry.set(_registry)
_registry.init_registry()

data = {"location": {"lat": 12.3, "lng": 13.2}}
event = Event(**data)

This must not give a missing error for the field name from Location class. The Event is not extendable. Therefore, even if a nested class is extendable, the type definition must be the one defined at writing time and we will never try to resolve nested types.