from pydantic import BaseModel
class Grafana(BaseModel):
user: str
password: str
host: str
port: str
api_key: str | None = None
GRAFANA_URL = f"http://{user}:{password}@{host}:{port}"
API_DATASOURCES = "/api/datasources"
API_KEYS = "/api/auth/keys"
With Pydantic I get two unbound variables error messages for user
, password
, etc. in GRAFANA_URL
.
Is there a way to solve this? In a regular class, I would just create GRAFANA_URL
in the __init__
method. With Pydantic, I'm not sure how to proceed.
In Pydantic version 2 you can define a computed field for this exact purpose.
from pydantic import BaseModel, computed_field
class Model(BaseModel):
foo: str
bar: str
@computed_field
@property
def foobar(self) -> str:
return self.foo + self.bar
obj = Model(foo="a", bar="b")
print(obj) # foo='a' bar='b' foobar='ab'
One nice thing about this is that foobar
will be part of the the serialization schema, but not part of the validation schema. Roughly speaking, this means that the foobar
field will be part of a model instance, when it is dumped/returned somewhere, but no foobar
value is expected for constructing a model instance. This is very useful when for example generating OpenAPI documentations from your models.
Demo, with the Model
from above:
...
import json
schema_val = Model.model_json_schema(mode="validation")
schema_ser = Model.model_json_schema(mode="serialization")
print(json.dumps(schema_val, indent=4))
print(json.dumps(schema_ser, indent=4))
Output:
{
"properties": {
"foo": {
"title": "Foo",
"type": "string"
},
"bar": {
"title": "Bar",
"type": "string"
}
},
"required": [
"foo",
"bar"
],
"title": "Model",
"type": "object"
}
{
"properties": {
"foo": {
"title": "Foo",
"type": "string"
},
"bar": {
"title": "Bar",
"type": "string"
},
"foobar": {
"readOnly": true,
"title": "Foobar",
"type": "string"
}
},
"required": [
"foo",
"bar",
"foobar"
],
"title": "Model",
"type": "object"
}
See the API reference for the computed_field
decorator for additional options.
@validator
See the validators documentation for details.
from typing import Any
from pydantic import BaseModel, validator
class Model(BaseModel):
foo: str
bar: str
foobar: str = ""
@validator("foobar", always=True)
def set_if_empty(cls, v: str, values: dict[str, Any]) -> str:
if v == "":
return values["foo"] + values["bar"]
return v
obj = Model(foo="a", bar="b")
print(obj) # foo='a' bar='b' foobar='ab'
That way foobar
remains a regular model field.
Note that for this to work, foobar
must be defined after foo
and bar
. Otherwise you will have to use a root validator.
PS: This approach also works analogously with Pydantic v2 @field_validator
and @model_validator
.
@property
from pydantic import BaseModel
class Model(BaseModel):
foo: str
bar: str
@property
def foobar(self) -> str:
return self.foo + self.bar
obj = Model(foo="a", bar="b")
print(obj) # foo='a' bar='b'
print(obj.foobar) # ab
Then foobar
will not be a model field anymore and therefore not part of the schema. That may or may not be relevant to you.
PS: This of course also works with Pydantic v2, though there probably is no benefit of using @property
without @computed_field
(see above).