How do add an assembled field to a Pydantic model
Question:
Say I have model
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
How do I make another model that is constructed from this one and has a field that changes based on the fields in this model?
For instance, something like this
class User(BaseModel):
full_name: str = first_name + ' ' + last_name
Constructed like this maybe
User.parse_obj(UserDB)
Thanks!
Answers:
If you do not want to keep first_name
and last_name
in User
then you can
- customize
__init__
.
- use validator for setting
full_name
.
Both methods do what you want:
from typing import Optional
from pydantic import BaseModel, validator
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
class User_1(BaseModel):
location: str # for a change
full_name: Optional[str] = None
def __init__(self, user_db: UserDB, **data):
super().__init__(full_name=f"{user_db.first_name} {user_db.last_name}", **data)
user_db = UserDB(first_name="John", last_name="Stark")
user = User_1(user_db, location="Mars")
print(user)
class User_2(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
full_name: Optional[str] = None
@validator('full_name', always=True)
def ab(cls, v, values) -> str:
return f"{values['first_name']} {values['last_name']}"
user = User_2(**user_db.dict())
print(user)
output
location='Mars' full_name='John Stark'
first_name='John' last_name='Stark' full_name='John Stark'
UPDATE:
For working with response_model
you can customize __init__
in such way:
class User_1(BaseModel):
location: str # for a change
full_name: Optional[str] = None
# def __init__(self, user_db: UserDB, **data):
def __init__(self, first_name, last_name, **data):
super().__init__(full_name=f"{first_name} {last_name}", **data)
user_db = UserDB(first_name="John", last_name="Stark")
user = User_1(**user_db.dict(), location="Mars")
print(user)
I created a pip package that seems to do exactly what you need. Here is the link: https://pypi.org/project/pydantic-computed/
Your example would then look like this:
from pydantic import BaseModel
from pydantic_computed import Computed, computed
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
class User(UserDB):
full_name: Computed[str]
@computed('full_name')
def compute_full_name(first_name: str, last_name: str):
return first_name + ' ' + last_name
# parsing also works as normal:
user_db = UserDB(first_name='John', last_name='Doe')
user = User.parse_obj(user_db)
print(user.full_name) # Outputs "John Doe"
This will also work for response_model (e.g. in FastAPI) since the computed value is actually set on the full_name property.
Say I have model
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
How do I make another model that is constructed from this one and has a field that changes based on the fields in this model?
For instance, something like this
class User(BaseModel):
full_name: str = first_name + ' ' + last_name
Constructed like this maybe
User.parse_obj(UserDB)
Thanks!
If you do not want to keep first_name
and last_name
in User
then you can
- customize
__init__
. - use validator for setting
full_name
.
Both methods do what you want:
from typing import Optional
from pydantic import BaseModel, validator
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
class User_1(BaseModel):
location: str # for a change
full_name: Optional[str] = None
def __init__(self, user_db: UserDB, **data):
super().__init__(full_name=f"{user_db.first_name} {user_db.last_name}", **data)
user_db = UserDB(first_name="John", last_name="Stark")
user = User_1(user_db, location="Mars")
print(user)
class User_2(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
full_name: Optional[str] = None
@validator('full_name', always=True)
def ab(cls, v, values) -> str:
return f"{values['first_name']} {values['last_name']}"
user = User_2(**user_db.dict())
print(user)
output
location='Mars' full_name='John Stark'
first_name='John' last_name='Stark' full_name='John Stark'
UPDATE:
For working with response_model
you can customize __init__
in such way:
class User_1(BaseModel):
location: str # for a change
full_name: Optional[str] = None
# def __init__(self, user_db: UserDB, **data):
def __init__(self, first_name, last_name, **data):
super().__init__(full_name=f"{first_name} {last_name}", **data)
user_db = UserDB(first_name="John", last_name="Stark")
user = User_1(**user_db.dict(), location="Mars")
print(user)
I created a pip package that seems to do exactly what you need. Here is the link: https://pypi.org/project/pydantic-computed/
Your example would then look like this:
from pydantic import BaseModel
from pydantic_computed import Computed, computed
class UserDB(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
class User(UserDB):
full_name: Computed[str]
@computed('full_name')
def compute_full_name(first_name: str, last_name: str):
return first_name + ' ' + last_name
# parsing also works as normal:
user_db = UserDB(first_name='John', last_name='Doe')
user = User.parse_obj(user_db)
print(user.full_name) # Outputs "John Doe"
This will also work for response_model (e.g. in FastAPI) since the computed value is actually set on the full_name property.