Created
March 28, 2024 18:00
-
-
Save mattwthompson/4e873bf0f2dc6c71f50fa9388d57dfae to your computer and use it in GitHub Desktop.
Quick example using annotated types for Pint/Pydantic v2 compatibility
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from openff.units import Quantity | |
from typing import Annotated | |
from pydantic import ( | |
BeforeValidator, | |
AfterValidator, | |
BaseModel, | |
ValidationError, | |
PositiveFloat, | |
) | |
from functools import partial | |
def to_quantity(quantity: Quantity | str) -> Quantity: | |
try: | |
return Quantity(quantity) | |
except Exception as error: | |
raise ValueError from error | |
def has_compatible_dimensionality(quantity: Quantity, unit: str) -> Quantity: | |
if quantity.is_compatible_with(unit): | |
return quantity | |
else: | |
raise ValueError( | |
f"Dimensionality must be compatible with 'unit'", | |
) | |
is_compatible_with_length = partial(has_compatible_dimensionality, unit="angstrom") | |
is_compatible_with_mass = partial(has_compatible_dimensionality, unit="dalton") | |
is_compatible_with_temperature = partial(has_compatible_dimensionality, unit="kelvin") | |
is_compatible_with_pressure = partial(has_compatible_dimensionality, unit="atm") | |
LengthQuantity = Annotated[ | |
Quantity, | |
BeforeValidator(to_quantity), | |
AfterValidator(is_compatible_with_length), | |
] | |
MassQuantity = Annotated[ | |
Quantity, | |
BeforeValidator(to_quantity), | |
AfterValidator(is_compatible_with_mass), | |
] | |
TemperatureQuantity = Annotated[ | |
Quantity, | |
BeforeValidator(to_quantity), | |
AfterValidator(is_compatible_with_temperature), | |
] | |
PressureQuantity = Annotated[ | |
Quantity, | |
BeforeValidator(to_quantity), | |
AfterValidator(is_compatible_with_pressure), | |
] | |
""" | |
Downstream code would something like | |
""" | |
# from openff.somewhere import TemperatureQuantity, PressureQuantity | |
from pydantic import BaseModel, PositiveFloat, Field | |
class DownstreamBaseModel(BaseModel): | |
class Config: | |
arbitrary_types_allowed = True | |
# other stuff ... | |
class ThermoSettings(DownstreamBaseModel): | |
temperature: TemperatureQuantity = Field(description="Simulation temperature") | |
pressure: PressureQuantity = Field(description="Simulation pressure") | |
ph: PositiveFloat | None = Field(None, description="Simulation pH") | |
redox_potential: float | None = Field( | |
None, description="Simulation redox potential" | |
) | |
print( | |
ThermoSettings( | |
temperature=Quantity("300 K"), pressure=Quantity("1 atm") | |
).model_dump() | |
) | |
# {'temperature': <Quantity(300, 'kelvin')>, 'pressure': <Quantity(1, 'standard_atmosphere')>, 'ph': None, 'redox_potential': None} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment