Pydantic: [QUESTION]: Any of many types

Created on 10 May 2019  路  7Comments  路  Source: samuelcolvin/pydantic

Question

For questions:

  • OS: Linux Ubuntu
  • Python version import sys; print(sys.version): Python 3.7.3
  • Pydantic version import pydantic; print(pydantic.VERSION): 0.23

I have a model component which is a polymorphic class for class : Device, VRF, Vlan, Interface and others.

declared as below in SQLAlchemy


class Component(Base):
    __tablename__ = "component"
    id = Column(Integer, primary_key=True, index=True)
    discriminator = Column(String)

    __mapper_args__ = {
        "polymorphic_on": discriminator,
        'with_polymorphic': '*'
    }


class Device(Component):
    __tablename__ = "device"
    id = Column(Integer, ForeignKey(Component.id), primary_key=True)
    fqdn = Column(String, index=True)
    name = Column(String, unique=True, index=True)
    role = Column(Enum(RoleEnum), nullable=False)
    type = Column(Enum(TypeEnum), nullable=False)
    serial = Column(String, unique=True)
    version = Column(String)
    management_ip = Column(String, unique=True)
    brand_id = Column(Integer, ForeignKey("brand.id"))
    location_id = Column(Integer, ForeignKey("location.id"))
    owner_id = Column(Integer, ForeignKey("customer.id"))

    brand = relationship("Brand")
    location = relationship("Location")
    owner = relationship("Customer")
    created_at = Column(DateTime, default=datetime.datetime.now)
    updated_at = Column(DateTime, default=datetime.datetime.now)

    __mapper_args__ = {
        'polymorphic_load': 'selectin',
        "polymorphic_identity": "Device"
    }

    def __repr__(self):
        return "<Device(id='%d', name='%s', fqdn='%s')>" % (
            self.id,
            self.name,
            self.fqdn,
        )

class Interface(Component):
    __tablename__ = "interface"
    id = Column(Integer, ForeignKey(Component.id), primary_key=True)
    name = Column(String, index=True)
    description = Column(String)
    role = Column(Enum(RoleEnum), nullable=False)
    ip_address_id = Column(Integer, ForeignKey("ip_address.id"))

    ip_address = relationship("Ip_Address")
    # infrastructures = relationship("InfrastructureInterface")

    created_at = Column(DateTime, default=datetime.datetime.now)
    updated_at = Column(DateTime, default=datetime.datetime.now)

    __mapper_args__ = {
        'polymorphic_load': 'selectin',
        "polymorphic_identity": "Interface"
    }

    def __repr__(self):
        return "<Interface(id='%d', name='%s', role='%s')>" % (
            self.id if self.id is not None else 0,
            self.name,
            self.role,
        )
...

in Pydantic Model: I would like to have a class component which could receive any of Class

currently I did that:


# Additional properties to return via API
class Infrastructure(InfrastructureBaseInDB):
    components: Optional[List[Union[DeviceBase, VRFBase, VlanBase, PeerBase, InterfaceBase]]] = None

Pydantic manage all my object but with the same class not in polymorphic, so it use DeviceBase

result is bad (Part)

 "components": [
      {
        "fqdn": "mydevice.int.name.com",
        "name": "mydevice",
        "role": "none",
        "type": "router",
        "serial": "mydevice",
        "version": "",
        "brand": null,
        "location": null,
        "owner": null
      },
      {
        "fqdn": null,
        "name": "WAN_VRF",
        "role": "wan",
        "type": null,
        "serial": null,
        "version": null,
        "brand": null,
        "location": null,
        "owner": null
      },
      {
        "fqdn": null,
        "name": "ITFW_N1_TO_N1",
        "role": null,
        "type": null,
        "serial": null,
        "version": null,
        "brand": null,
        "location": null,
        "owner": null
      },

Do you know how to have many models in the same list ?

Thanks in advance,

documentation question

Most helpful comment

@meandus the models (classes) in the Union are checked in order. So, if you put first a model that has fewer properties but still matches, it will be used. Discarding the other models.

So, you should put the most specific models first (the ones with more required attributes).

All 7 comments

@meandus the models (classes) in the Union are checked in order. So, if you put first a model that has fewer properties but still matches, it will be used. Discarding the other models.

So, you should put the most specific models first (the ones with more required attributes).

thanks for answering @tiangolo

@tiangolo @samuelcolvin this one was a tricky one to figure out and if done incorrectly breaks the provided values, e.g.:

import uuid
from typing import Union

from pydantic import BaseModel


class Something(BaseModel):

    id: Union[int, str, uuid.UUID]
    type: str


obj = Something(id=uuid.uuid4(), type="something")
# >>> obj
# <Something id=298701003283496140679204034245714674497 type='something'>


class SomethingRight(BaseModel):

    id: Union[uuid.UUID, int, str]
    type: str


obj_right = SomethingRight(id=uuid.uuid4(), type="something_right")
# >>> obj_right
# <SomethingRight id=UUID('56452b0f-c03a-429a-b318-16a528505baa') type='something_right'>

Would it be possible to document this behaviour cause as you can see the UUID was converted to it int representation?

I think it is documented, but is love a PR to make it clearer.

@samuelcolvin would be happy to prep a PR, is there some section in the doco you'd like it under or should i make a Gotchas section under index.rst?

Humm, I don't really like faq/gotcha sections they end up as a bin for unstructured content.

Best to add a dedicated "Union" section somewhere.

You're probs right, will prep now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Gaunt picture Gaunt  路  19Comments

Yolley picture Yolley  路  18Comments

bradodarb picture bradodarb  路  22Comments

dmfigol picture dmfigol  路  38Comments

kryft picture kryft  路  35Comments