I quite frequently run into situations where I want to reuse the same field in different models, sharing attributes about them.
What is the recommended approach to do this? I wonder if this might be worth adding something to the documentation about?
I can think of two ways:
Field object between modelsI think this is probably the worse choice because it doesn't introduce a #ref for the shared field in the schema, but inlines it. However, it does seem slightly more natural in Pydantic.
from pydantic import BaseModel, Field
sharedField = Field("my-default", example="hello I am an example", description="An example field.", nullable=True)
class ModelOne(BaseModel):
"""
Some documentation about ModelOne
"""
myField:str = sharedField
class ModelTwo(BaseModel):
"""
Some documentation about ModelTwo
"""
myField:str = sharedField
__root__ typefrom pydantic import BaseModel, Field
class SharedField(BaseModel):
"""
Some documentation about SharedField
"""
__root__: str
class Config:
schema_extra = {
"example": "example of SharedField",
"nullable": True
}
class ModelOne(BaseModel):
"""
Some documentation about ModelOne
"""
myField:SharedField
class ModelTwo(BaseModel):
"""
Some documentation about ModelTwo
"""
myField:SharedField
I currently have an API schema defined using Python dictionaries. I wrote some code to turn those dictionaries into OpenAPI and JSONSchema compatible specs.
I'm trying to replace some of my code with Pydantic, since I prefer the approach you have taken, and also because it seems quite polished already. Thanks for making it.
import sys; print(sys.version): 3.7.5import pydantic; print(pydantic.VERSION): 1.0There are two other options:
Make a custom type. Depending on what you want to do, this may be easier to work with and more performant than using a __root__ model.
Inheritance:
class ModelOne(BaseModel):
"""
Some documentation about ModelOne
"""
myField:str = sharedField
class ModelTwo(ModelOne):
"""
Some documentation about ModelTwo
"""
# myField already present due to inheritance
myOtherField: str
You can also use a mixin class if that better reflects your data hierarchy:
class MyFieldMixin(BaseModel):
myField: str = sharedField # defined in your example
class ModelOne(MyFieldMixin):
"""
Some documentation about ModelOne
"""
class ModelTwo(MyFieldMixin):
"""
Some documentation about ModelTwo
"""
I'd agree with @dmontagu that inheritance was probably the best way.
Let me know if somehow that doesn't work.
Thanks to both of you for your advice.
I will try out the mixin approach and see if that works.
What about more complex types? __root__ doesn't seem to support Field.
This example produces error for me:
class User(BaseModel):
nickname: Name
class Name(BaseModel):
__root__: str = Field(..., regex=r".+")
# AssertionError: Path params must be of one of the supported types
I cant use inheritance because I want to use Name type separately in a few places in fastapi.
Am I doing something wrong here?
NVM, the error was coming from fastapi, I had to replace nickaname = Name with nickname: str = Name
So with the help of https://pydantic-docs.helpmanual.io/usage/types/#classes-with-__get_validators__
I came up with something like this, not sure if this is an optimal solution:
class _RegexName(str):
@classmethod
def _get_pattern(cls) -> re.Pattern:
raise NotImplementedError
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v: Any) -> _RegexName:
if not isinstance(v, str):
raise TypeError("string required")
if not cls._get_pattern().fullmatch(v):
raise ValueError(f"invalid {cls.__name__} format")
return cls(v)
class Name(_RegexName):
@classmethod
def _get_pattern(cls) -> re.Pattern:
return _NAME_REGEX
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(
pattern=_NAME_REGEX.pattern,
examples=["my-name", "hello123"],
)