Validators
Both QueryParams and Data models can benefit from the tactical use of Pydantic validators. They assist with enforcing FastAPI compliance for both inputs and outputs, and they work in the final stage of transformation immediately before output.
Some situations where they are used include:
- Transform, conform, or otherwise alter the entered query parameter or returned data value.
- Handle type checking and formatting.
- Setting default values.
- Normalizing percent values.
- Cleaning NaN values.
- Validating unbound string parameters.
Importing
The items to import are from the Pydantic library:
from pydantic import field_validator, model_validator
Field validators work at the row-level and do not access other parts of the model.
Use model_validator to work with the entire row.
Examples
These are a few examples, functions will be inside the class code block definition. Some static type checkers may disagree with the Pydantic patterns.
Parsing Dates
Providers will format dates in a number of ways. OpenBB uses YYYY-MM-DD as the standard convention for inputs.
Outputs are a datetime object or a valid ISO date string.
@field_validator("last_trade_timestamp", mode="before", check_fields=False)
@classmethod
def _parse_timestamp(cls, v):
"""Parse a Unix timestamp."""
return datetime.fromtimestamp(v)
Normalize Percent Values
At the provider level, we want to standardize the way values representing a percent are returned.
It is our intention to ensure those values are ready-to-consume by formulas without conversion.
This example would be used within a provider's Data model.
@field_validator("change_percent", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize the percent."""
return v / 100 if v else None
Dynamic Default Date
It might be desirable to have a default date parameter that is not static. To allow this, we must set the default parameter value as None, and use the model_validator. This example is for the QueryParams.
@model_validator(mode="before")
@classmethod
def _validate_dates(cls, values):
"""Validate the query parameters."""
if values.get("start_date") is None:
values["start_date"] = (datetime.now() - timedelta(days=90)).date()
if values.get("end_date") is None:
values["end_date"] = datetime.now().date()
return values
Replace 0s With None
Sometimes values are returned as a 0 when they should really be a null.
This example looks at the entire Data model, but could easily be adapted to use on individual fields.
@model_validator(mode="before")
@classmethod
def replace_zero(cls, values):
"""Check for zero values and replace with None."""
return (
{k: None if v == 0 else v for k, v in values.items()}
if isinstance(values, dict)
else values
)