Pydantic
Pydantic is used to enforce type hints at runtime, and provides user friendly errors when data is invalid.
- Parsing data with Pydantic
- Modern Python validation and settings with Pydantic
- Pydantic - library for creating self-validating data models
Models
The primary means of defining objects in pydantic is via models (models are classes which inherit from BaseModel). Models work (conceptually) the same as types in strictly typed languages. The idea is that untrusted data can be passed to a model, and after parsing and validation pydantic guarantees that the fields of the resultant model instance will conform to the field types defined on the model.
In the example below are we fetching some account details and want to make sure that the system and username are of type `StrictStr` and the password is a `SecretStr`. The difference between StrictStr and str is that pydantic will complain if the value is not a string when using StrictStr. With only str will pydantic cast the value into a string if it can and only complain if it can't.
Model Signature
All pydantic models will have their [signature generated based on their fields](https://docs.pydantic.dev/usage/models/#model-signature). An accurate signature is useful for introspection purposes.
To be included in the signature, a field's alias or name must be a valid Python identifier. pydantic prefers aliases over names, but may use field names if the alias is not a valid Python identifier.
Field Order
Field order is important in models for the following reasons:
- validation is performed in the order fields are defined; fields validators can access the values of earlier fields, but not later ones
- field order is preserved in the model schema
- field order is preserved in validation errors
- field order is preserved by .dict() and .json() functions.
Data Conversion
pydantic will try to cast input data into the model field types. In case the model type permits less information then the amount of data provided, will this lead to data loss.
class Model(BaseModel) a: int
a = Model(a=123.45) # Returns 123
[Strict type checking](https://docs.pydantic.dev/usage/types/#strict-types) is partially supported.
Required Fields
You can declare a field as required in three different ways. Either by just providing the type, or a type and an ellipses or the Field function followed by ellipses.
from pydantic import BaseModel, Field
class Model(BaseModel): a: int b: int = … c: int = Field(…)
To specify that a field is required but that the value can be Null can you use the `None` value. The same three options to write the same is possible.
class Model(BaseModel): a: int | None b: int | None = … c: int | None = Field(…)
The Field function can take many different arguments to provide extra [customization](#field-customization) to the information.
Default Values
A model can have default properties which are defined either with the assignment operator or the [Field customization](#field-customization) `default`.
If you want the default to be generated dynamically can you specify that through the Field `defaultfactory` argument and add a custom function.
Model Methods and Properties
Models possess the following methods and attributes:
dict()
: returns a dictionary of the model's fields and values; cf. exporting modelsjson()
: returns a JSON string representation dict()copy()
: returns a copy (by default, [shallow copy](list.md)) of the modelparse_obj()
: a utility for loading any object into a model with error handling if the object is not a dictionaryparse_raw()
: a utility for loading strings of numerous formatsparse_file()
: like parse<sub>raw</sub>() but for file pathsfrom_orm()
: loads data into a model from an arbitrary classschema()
: returns a dictionary representing the model as JSON Schemaschema_json()
: returns a JSON string representation of schema()construct()
: a class method for creating models without running validation__fields_set__
: Set of names of fields which were set when the model instance was initialised__fields__
: a dictionary of the model's fields__config__
: the configuration class for the model
Private Model Attributes
Limit attribute scope
Exclude attributes
Include attributes
Recursive Models
https://docs.pydantic.dev/usage/models/#recursive-models
Below is some Json data and then a data class enhanced with Pydantic's BaseModel. The model is using [recursive models](https://docs.pydantic.dev/usage/models/#recursive-models).
from pydantic import BaseModel from typing import Optional
data = { "name": "Max", "age": "55", "location": { "city": "Stockholm", "state": "Stockholm" }, "bike": "best bike", "rides": [7, 3, 12, 14, "12"], "bikebooks": [ { "isin": "23232123459", "title": "Riding my bike", "subtitle": "And other musings" }, { "isin": "2323212345", "title": "Riding my bike again", "subtitle": "No more musings" }, ] }
class Location(BaseModel): city: str state: str country: Optional[str] = "Mariehamn" # Sets the default to "Mariehamn"
@validator('city') @classmethod def cityismariehamn(cls, v): if v != 'Mariehamn': raise ValueError('City must be "Mariehamn".') return v.title()
class Book(BaseModel): isin: int title: str subtitle: str
@validator('isin') @classmethod def isinmustbecorrectlength(cls, v): if len(str(v)) != 11: raise ValueError('isin must be 11 characters long') return v
class User(BaseModel): name: str location: Optional[Location] = None bike: str rides: list[int] = [] bikebooks: list[Book] = [Book(**book) for book in data.get('bikebooks')]
user = User(**data)
Reserved Names
If you encounter reserved names for your data classes, then can you use an [alias](https://docs.pydantic.dev/usage/models/#reserved-names). This is for example useful if you use Pydantic with an ORM or if you want to expose names as camelCase but internally use snake\case.
class MyModel(BaseModel): metadata: dict[str, str] = Field(alias='metadata_')
class Config: ormmode = True
Alias precedendence
https://docs.pydantic.dev/usage/model_config/#alias-precedence
<a id="org3f29da8"></a>
## Construct
The [construct() method](https://docs.pydantic.dev/usage/models/#creating-models-without-validation) allows models to be created without validation. This can be useful when data has already been validated or comes from a trusted source and you want to create a model as efficiently as possible (construct() is generally around 30x faster than creating a model with full validation).
<a id="orgfab40f8"></a>
## Create Model from NamedTuple or TypedDict
Sometimes you already use in your application classes that inherit from NamedTuple or TypedDict and you don't want to duplicate all your information to have a BaseModel. For this pydantic provides [create<sub>model</sub><sub>from</sub><sub>namedtuple</sub> and create<sub>model</sub><sub>from</sub><sub>typeddict</sub> methods](https://docs.pydantic.dev/usage/models/#model-creation-from-namedtuple-or-typeddict). Those methods have the exact same keyword arguments as create<sub>model</sub>.
<a id="org90bf254"></a>
## Custom Root Types
Pydantic models can be defined with a [custom root type](https://docs.pydantic.dev/usage/models/#custom-root-types) by declaring the <span class="underline"><span class="underline">root</span></span> field.
The root type can be any type supported by pydantic, and is specified by the type hint on the <span class="underline"><span class="underline">root</span></span> field. The root value can be passed to the model <span class="underline"><span class="underline">init</span></span> via the <span class="underline"><span class="underline">root</span></span> keyword argument, or as the first and only argument to parse<sub>obj</sub>.
If you call the parse<sub>obj</sub> method for a model with a custom root type with a dict as the first argument, the following logic is used:
If the custom root type is a mapping type (eg., Dict or Mapping), the argument itself is always validated against the custom root type. For other custom root types, if the dict has precisely one key with the value <span class="underline"><span class="underline">root</span></span>, the corresponding value will be validated against the custom root type. Otherwise, the dict itself is validated against the custom root type.
<a id="org18adef4"></a>
## Faux Immutability
[Models can be configured to be immutable](https://docs.pydantic.dev/usage/models/#faux-immutability) via allow\mutation = False. When this is set, attempting to change the values of instance attributes will raise errors. See model config for more details on Config.
<a id="org0adeaa3"></a>
## Generic Models
The [generic models](https://docs.pydantic.dev/usage/models/#generic-models) makes it easier to reuse a common model structure.
In order to declare a generic model, you perform the following steps:
- Declare one or more typing.TypeVar instances to use to parameterize your model.
- Declare a pydantic model that inherits from pydantic.generics.GenericModel and typing.Generic, where you pass the TypeVar instances as parameters to typing.Generic.
- Use the TypeVar instances as annotations where you will want to replace them with other types or pydantic models.
If you set Config or make use of validator in your generic model definition, it is applied to concrete subclasses in the same way as when inheriting from BaseModel. Any methods defined on your generic class will also be inherited.
<a id="org929a3c1"></a>
## Dynamic Model Creation
There are some occasions where the shape of a model is not known until runtime. For this pydantic provides the [create<sub>model</sub> method to allow models to be created on the fly](https://docs.pydantic.dev/usage/models/#dynamic-model-creation).
<a id="org92cb9d4"></a>
## Abstract Base Classes
Pydantic models can be used alongside Python's [Abstract Base Classes](https://docs.pydantic.dev/usage/models/#abstract-base-classes) (ABCs).
<a id="org922ba25"></a>
## Structural Pattern Matching
https://docs.pydantic.dev/usage/models/#structural-pattern-matching
<a id="org5c1f69c"></a>
## Postponed annotations
https://docs.pydantic.dev/usage/postponed_annotations/
<a id="org54f73be"></a>
https://docs.pydantic.dev/usage/model_config/
<a id="org04c305f"></a>
## Change behaviour globally
https://docs.pydantic.dev/usage/model_config/#change-behaviour-globally
<a id="org8f802ef"></a>
## Alias Generator
https://docs.pydantic.dev/usage/model_config/#alias-generator
<a id="orgee271f8"></a>
## Alias precedence
https://docs.pydantic.dev/usage/model_config/#alias-precedence
See also Models.
<a id="org3e25452"></a>
## Smart union
https://docs.pydantic.dev/usage/model_config/#smart-union
<a id="org5659c83"></a>
Pydantic provides three [classmethod helper functions](https://docs.pydantic.dev/usage/models/#helper-functions) on models for parsing data:
- `parseobj` this is very similar to the `_init__` method of the model, except it takes a dict rather than keyword arguments. If the object passed is not a dict a ValidationError will be raised.
- `parseraw` this takes a str or bytes and parses it as json, then passes the result to `parseobj`. Parsing pickle data is also supported by setting the content\type argument appropriately.
- `parsefile` this takes in a file path, reads the file and passes the contents to `parseraw`. If content\type is omitted, it is inferred from the file's extension.
<a id="orgf20576f"></a>
## Parsing data into a specified type
https://docs.pydantic.dev/usage/models/#parsing-data-into-a-specified-type
<a id="org05241c6"></a>
<a id="field-customization"></a>
## Field Customizations
The field function can be used to provide extra information about the field and validations. The field function has the following arguments:
- `default`: (a positional argument) the default value of the field. Since the Field replaces the field's default, this first argument can be used to set the default. Use ellipsis (…) to indicate the field is required.
- `defaultfactory`: a zero-argument callable that will be called when a default value is needed for this field. Among other purposes, this can be used to set dynamic default values. It is forbidden to set both default and default<sub>factory</sub>.
- `alias`: the public name of the field
- `title`: if omitted, field<sub>name.title</sub>() is used
- `description`: if omitted and the annotation is a sub-model, the docstring of the sub-model will be used
- `exclude`: exclude this field when dumping (.dict and .json) the instance. The exact syntax and configuration options are described in details in the exporting models section.
- `include`: include (only) this field when dumping (.dict and .json) the instance. The exact syntax and configuration options are described in details in the exporting models section.
- `const`: this argument must be the same as the field's default value if present.
- `gt`: for numeric values (int, float, Decimal), adds a validation of "greater than" and an annotation of exclusiveMinimum to the JSON Schema
- `ge`: for numeric values, this adds a validation of "greater than or equal" and an annotation of minimum to the JSON Schema
- `lt`: for numeric values, this adds a validation of "less than" and an annotation of exclusiveMaximum to the JSON Schema
- `le`: for numeric values, this adds a validation of "less than or equal" and an annotation of maximum to the JSON Schema
- `multipleof`: for numeric values, this adds a validation of "a multiple of" and an annotation of multipleOf to the JSON Schema
- `maxdigits`: for Decimal values, this adds a validation to have a maximum number of digits within the decimal. It does not include a zero before the decimal point or trailing decimal zeroes.
- `decimalplaces`: for Decimal values, this adds a validation to have at most a number of decimal places allowed. It does not include trailing decimal zeroes.
- `minitems`: for list values, this adds a corresponding validation and an annotation of minItems to the JSON Schema
- `maxitems`: for list values, this adds a corresponding validation and an annotation of maxItems to the JSON Schema
- `uniqueitems`: for list values, this adds a corresponding validation and an annotation of uniqueItems to the JSON Schema
- `minlength`: for string values, this adds a corresponding validation and an annotation of minLength to the JSON Schema
- `maxlength`: for string values, this adds a corresponding validation and an annotation of maxLength to the JSON Schema
- `allowmutation`: a boolean which defaults to True. When False, the field raises a TypeError if the field is assigned on an instance. The model config must set validate<sub>assignment</sub> to True for this check to be performed.
- `regex`: for string values, this adds a Regular Expression validation generated from the passed string and an annotation of pattern to the JSON Schema
pydantic validates strings using re.match, which treats regular expressions as implicitly anchored at the beginning. On the contrary, JSON Schema validators treat the pattern keyword as implicitly unanchored, more like what re.search does.
For interoperability, depending on your desired behavior, either explicitly anchor your regular expressions with ^ (e.g. ^foo to match any string starting with foo), or explicitly allow an arbitrary prefix with .\*? (e.g. .\*?foo to match any string containing the substring foo).
It is possible to use the Config class to set the field properties as well. The only property which is it not possible to set through the configuration is the `default` property.
<a id="orgcfb6219"></a>
## Standard Library Types
https://docs.pydantic.dev/usage/types/#standard-library-types
<a id="orgf3b8472"></a>
## Arbitrary Types Allowed Config
https://docs.pydantic.dev/usage/types/#arbitrary-types-allowed
<a id="orgc2b7ae7"></a>
## Literal Type (only certain values)
https://docs.pydantic.dev/usage/types/#literal-type
One benefit of this field type is that it can be used to check for equality with one or more specific values without needing to declare custom validators.
<a id="org2be4f29"></a>
## Typing Iterables / Complex objects
https://docs.pydantic.dev/usage/types/#typing-iterables
<a id="org9ccfaeb"></a>
## Infinite Generators
https://docs.pydantic.dev/usage/types/#infinite-generators
<a id="org1584709"></a>
## Unions
https://docs.pydantic.dev/usage/types/#unions
<a id="org57c2512"></a>
## Enums
https://docs.pydantic.dev/usage/types/#enums-and-choices
<a id="org11ee131"></a>
## Date Time
https://docs.pydantic.dev/usage/types/#datetime-types
<a id="org22ab145"></a>
## Booleans
https://docs.pydantic.dev/usage/types/#booleans
<a id="org82daf91"></a>
## Callable
https://docs.pydantic.dev/usage/types/#callable
<a id="org66d1b11"></a>
## Type (only Classes, not instances)
https://docs.pydantic.dev/usage/types/#type
<a id="orgd347480"></a>
## TypeVar
https://docs.pydantic.dev/usage/types/#typevar
<a id="orgbda1c93"></a>
## Annotated Types
https://docs.pydantic.dev/usage/types/#annotated-types
<a id="orgb33d7f6"></a>
## Typed Dict
https://docs.pydantic.dev/usage/types/#typeddict
<a id="orgc749b7b"></a>
## Pydantic Specific Types
https://docs.pydantic.dev/usage/types/#pydantic-types
<a id="org9f3662f"></a>
## Secret Types
https://docs.pydantic.dev/usage/types/#secret-types
<a id="orge460e7e"></a>
## Json type
https://docs.pydantic.dev/usage/types/#json-type
<a id="org4b401b3"></a>
## Payment card numbers
https://docs.pydantic.dev/usage/types/#payment-card-numbers
<a id="org8c490b4"></a>
## Constrained Types
https://docs.pydantic.dev/usage/types/#constrained-types
<a id="org56ffec9"></a>
## Strict Types
Use strict types to prevent casting from compatible types. These types will only pass validation when the validated value is of the respective type or is a subtype of that type.
- Strict Types:
- StrictStr
- StrictBytes
- StrictInt
- StrictFloat
- StrictBool
The same behavior is also exposed via the strict field of the constrained types which can be combined with a multitude of complex validation rules.
- Constrained Types:
- ConstrainedStr
- ConstrainedBytes
- ConstrainedFloat
- ConstrainedInt
The following caveats apply:
- `StrictBytes` (and the strict option of `ConstrainedBytes`) will accept both bytes, and bytearray types.
- `StrictInt` (and the strict option of `ConstrainedInt`) will not accept bool types, even though bool is a subclass of int in Python. Other subclasses will work.
- `StrictFloat` (and the strict option of `ConstrainedFloat`) will not accept int.
<a id="orge77c9ba"></a>
## ByteSize
https://docs.pydantic.dev/usage/types/#bytesize
<a id="org2541f34"></a>
## Custom Data Types
https://docs.pydantic.dev/usage/types/#custom-data-types
<a id="org1d427ec"></a>
## Generic Classes as Types
https://docs.pydantic.dev/usage/types/#generic-classes-as-types
<a id="orgccaa3bc"></a>
<a id="org077478e"></a>
## Validators
Custom validation and complex relationships between objects can be achieved using the validator decorator.
https://docs.pydantic.dev/usage/validators/
<a id="orge73c216"></a>
## Pre and per-item validators
https://docs.pydantic.dev/usage/validators/#pre-and-per-item-validators
<a id="org7738875"></a>
## Subclass validators and each<sub>item</sub>
https://docs.pydantic.dev/usage/validators/#subclass-validators-and-each_item
<a id="org965a5ef"></a>
## Validate always
https://docs.pydantic.dev/usage/validators/#validate-always
<a id="orgc0c23b2"></a>
## Reuse validators
Reuse a validator by setting `allowresuse=True`.
def setfloat(number: str) -> str: return number.replace(',', '.')
class Accountingrow(BaseModel): sum: float
_setfloat = validator('sum', allowreuse=True, checkfields=False)(setfloat)
summan = Accountingrow(sum='99,99')
<a id="orgc8570d0"></a>
## Root validators
It is possible to check the data class itself by using a root validator. https://docs.pydantic.dev/usage/validators/#root-validators
class Book(Basemodel): title: str subtitle: str
@rootvalidator(pre=True) @classmethod def checkthetile(cls, values): """Check if either title or subtitle exist.""" if "title" not in values and "subtitle" not in values: raise ValueError('The book need a title or subtitle.') return values
<a id="org2ca87fa"></a>
## Dataclass validators
https://docs.pydantic.dev/usage/validators/#dataclass-validators
<a id="org03416da"></a>
## Validation decorator
https://docs.pydantic.dev/usage/validation_decorator/
<a id="orgbcff8fc"></a>
## Validate Arguments
The validate<sub>arguments</sub> decorator allows the arguments passed to a function to be parsed and validated using the function's annotations before the function is called. While under the hood this uses the same approach of model creation and initialisation; it provides an extremely easy way to apply validation to your code with minimal boilerplate.
from pydantic import validatearguments, ValidationError
@validatearguments def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes: b = s.encode() return separator.join(b for _ in range(count))
a = repeat('hello', 3) print(a) #> b'hellohellohello'
b = repeat('x', '4', separator=' ') print(b) #> b'x x x x'
try: c = repeat('hello', 'wrong') except ValidationError as exc: print(exc) """ 1 validation error for Repeat count value is not a valid integer (type=typeerror.integer) """
<a id="org416a11d"></a>
### Argument Types
Argument types are inferred from type annotations on the function, arguments without a type decorator are considered as Any. Since validate<sub>arguments</sub> internally uses a standard BaseModel, all types listed in types can be validated, including pydantic models and custom types. As with the rest of pydantic, types can be coerced by the decorator before they're passed to the actual function:
import os from pathlib import Path from re import Pattern
from pydantic import validatearguments, DirectoryPath
@validatearguments def findfile(path: DirectoryPath, regex: Pattern, max=None) -> Path | None: for i, f in enumerate(path.glob('*/')): if max and i > max: return if f.isfile() and regex.fullmatch(str(f.relativeto(path))): return f
thisdir = os.path.dirname(file)
print(findfile(thisdir, 'validation.*')) #> home/runner/work/pydantic/pydantic/docs.tmpexamples/upgraded/validationde #> coratortypes310.py print(findfile(thisdir, 'foobar.*', max=3)) #> None
A few notes:
- Though they're passed as strings, path and regex are converted to a Path object and regex respectively by the decorator.
- Max has no type annotation, so will be considered as Any by the decorator.
- Type coercion like this can be extremely helpful but also confusing or not desired, see below for a discussion of validate<sub>arguments</sub>'s limitations in this regard.
<a id="org92caff3"></a>
## Validate Arguments
The validate<sub>arguments</sub> decorator allows the arguments passed to a function to be parsed and validated using the function's annotations before the function is called. While under the hood this uses the same approach of model creation and initialisation; it provides an extremely easy way to apply validation to your code with minimal boilerplate.
from pydantic import validatearguments, ValidationError
@validatearguments def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes: b = s.encode() return separator.join(b for _ in range(count))
a = repeat('hello', 3) print(a) #> b'hellohellohello'
b = repeat('x', '4', separator=' ') print(b) #> b'x x x x'
try: c = repeat('hello', 'wrong') except ValidationError as exc: print(exc) """ 1 validation error for Repeat count value is not a valid integer (type=typeerror.integer) """
<a id="org392cae7"></a>
### Argument Types
Argument types are inferred from type annotations on the function, arguments without a type decorator are considered as Any. Since validate<sub>arguments</sub> internally uses a standard BaseModel, all types listed in types can be validated, including pydantic models and custom types. As with the rest of pydantic, types can be coerced by the decorator before they're passed to the actual function:
import os from pathlib import Path from re import Pattern
from pydantic import validatearguments, DirectoryPath
@validatearguments def findfile(path: DirectoryPath, regex: Pattern, max=None) -> Path | None: for i, f in enumerate(path.glob('*/')): if max and i > max: return if f.isfile() and regex.fullmatch(str(f.relativeto(path))): return f
thisdir = os.path.dirname(file)
print(findfile(thisdir, 'validation.*')) #> home/runner/work/pydantic/pydantic/docs.tmpexamples/upgraded/validationde #> coratortypes310.py print(findfile(thisdir, 'foobar.*', max=3)) #> None
A few notes:
- Though they're passed as strings, path and regex are converted to a Path object and regex respectively by the decorator.
- Max has no type annotation, so will be considered as Any by the decorator.
- Type coercion like this can be extremely helpful but also confusing or not desired, see below for a discussion of validate<sub>arguments</sub>'s limitations in this regard.
<a id="orgf1e3670"></a>
Pydantic allows auto creation of JSON Schemas from models.
https://docs.pydantic.dev/usage/schema/
<a id="orgc142656"></a>
## Getting schema of a specified type.
https://docs.pydantic.dev/usage/schema/#getting-schema-of-a-specified-type
<a id="org49708a9"></a>
## Field customizations
https://docs.pydantic.dev/usage/schema/#field-customization
<a id="org670a6cd"></a>
## Unenforced Field constraints
https://docs.pydantic.dev/usage/schema/#unenforced-field-constraints
<a id="org88afded"></a>
## typing.Annotated Fields
https://docs.pydantic.dev/usage/schema/#typingannotated-fields
<a id="org81551d1"></a>
## Modifying schema in custom fields
https://docs.pydantic.dev/usage/schema/#modifying-schema-in-custom-fields
<a id="org3d58e0c"></a>
## JSON Schema Types
https://docs.pydantic.dev/usage/schema/#json-schema-types
<a id="orge0a097e"></a>
## Top-level schema generation
https://docs.pydantic.dev/usage/schema/#top-level-schema-generation
<a id="org83ba17e"></a>
## Schema customization
https://docs.pydantic.dev/usage/schema/#schema-customization
<a id="orge0d82ad"></a>
https://docs.pydantic.dev/usage/exporting_models/
<a id="orgd92bda6"></a>
## Include and exclude
<a id="orgc55ad74"></a>
https://docs.pydantic.dev/usage/models/#error-handling pydantic will raise ValidationError whenever it finds an error in the data it's validating.
Validation code should not raise ValidationError itself, but rather raise `ValueError`, `TypeError` or `AssertionError` (or subclasses of ValueError or TypeError) which will be caught and used to populate ValidationError.
One exception will be raised regardless of the number of errors found, that ValidationError will contain information about all the errors and how they happened.
You can access these errors in several ways:
- `e.errors()` : method will return list of errors found in the input data.
- `e.json()` : method will return a JSON representation of errors.
- `str(e)` : method will return a human readable representation of the errors.
Each error object contains:
- `loc` : the error's location as a list. The first item in the list will be the field where the error occurred, and if the field is a sub-model, subsequent items will be present to indicate the nested location of the error.
- `type` : a computer-readable identifier of the error type.
- `msg` : a human readable explanation of the error.
- `ctx` : an optional object which contains values required to render the error message.
<a id="org4530022"></a>
## Field checks
https://docs.pydantic.dev/usage/validators/#field-checks
<a id="org64e9e2c"></a>
## Custom Errors
https://docs.pydantic.dev/usage/models/#custom-errors
In your custom data types or validators you should use ValueError, TypeError or AssertionError to [raise (custom) errors](https://docs.pydantic.dev/usage/models/#custom-errors).
It is also possible to define your own custom error classes.
<a id="org00a819b"></a>
## Custom Errors
In your custom data types or validators you should use `ValueError`, `TypeError` or `AssertionError` to raise errors.
class NotABarError(PydanticValueError): code = 'notabar' msgtemplate = 'value is not "bar", got "{wrongvalue}"'
class Model(BaseModel): foo: str
@validator('foo') def valuemustequalbar(cls, v): if v != 'bar': raise NotABarError(wrongvalue=v) return v
try: Model(foo='ber') except ValidationError as e: print(e.json())
<a id="settings"></a>
https://docs.pydantic.dev/usage/settings/
<a id="org720c055"></a>
[pydantic-xml](https://pydantic-xml.readthedocs.io/en/latest/index.html) is a pydantic extension implementing model xml serialization (`toxml`) and deserialization (`fromxml`). It uses BaseXMLModel which inherits from the pydantic BaseModel class.
pydantic-xml tries to use the fastest xml parser in your system. It uses lxml if it is installed in your environment. To install pydantic-xml with lxml do install with `pip install pydantic-xml[lxml]`.
- Fields from primitive types (int, float, str, datetime, etc) are extracted from element text by default.
To alter the default behaviour should the field be marked as `attr` or `element`.
class Company(BaseXmlModel): tradename: str = attr(name='trade-name') # extracted from the 'trade-name' attribute website: HttpUrl = element() # extracted from the 'website' element text description: str # extracted from the root element text
<Company trade-name="SpaceX"> company description text <website>https://www.spacex.com</website> </Company>
The elements are by default using the strict [search mode](https://pydantic-xml.readthedocs.io/en/latest/pages/data-binding/elements.html#elements-search-mode). You can however apply the `searchmode='Ordered'` or `searchmode='Unordered` as well.
<a id="orgca2811c"></a>
## Generic XML Models
import datetime as dt import pathlib from typing import Generic, TypeVar from pydantic import HttpUrl from pydanticxml import BaseGenericXmlModel, BaseXmlModel, element
AuthType = TypeVar('AuthType')
class SoapHeader( BaseGenericXmlModel, Generic[AuthType], tag='Header', ns='soap',): auth: AuthType
class SoapMethod(BaseXmlModel): pass
MethodType = TypeVar('MethodType', bound=SoapMethod)
class SoapBody( BaseGenericXmlModel, Generic[MethodType], tag='Body', ns='soap',): call: MethodType
HeaderType = TypeVar('HeaderType', bound=SoapHeader) BodyType = TypeVar('BodyType', bound=SoapBody)
class SoapEnvelope( BaseGenericXmlModel, Generic[HeaderType, BodyType], tag='Envelope', ns='soap', nsmap={ 'soap': 'http://www.w3.org/2003/05/soap-envelope/', },): header: HeaderType body: BodyType
class BasicAuth( BaseXmlModel, tag='BasicAuth', ns='auth', nsmap={ 'auth': 'http://www.company.com/auth', },): user: str = element(tag='Username') password: str = element(tag='Password')
class CreateCompanyMethod( SoapMethod, tag='CreateCompany', ns='co', nsmap={ 'co': 'https://www.company.com/co', },): tradename: str = element(tag='TradeName') founded: dt.date = element(tag='Founded') website: HttpUrl = element(tag='WebSite')
CreateCompanyRequest = SoapEnvelope[ SoapHeader[ BasicAuth ], SoapBody[ CreateCompanyMethod ], ]
xmldoc = pathlib.Path('./doc.xml').readtext()
request = CreateCompanyRequest.fromxml(xmldoc)
assert request == CreateCompanyRequest.parsefile('./doc.json')
<a id="org840a4c0"></a>
https://docs.pydantic.dev/usage/models/#orm-mode-aka-arbitrary-class-instances
<a id="orgcf430b6"></a>
## Recursive ORM Models
https://docs.pydantic.dev/usage/models/#recursive-orm-models
<a id="org4fbbae3"></a>
## Data binding
https://docs.pydantic.dev/usage/models/#data-binding Arbitrary classes are processed by pydantic using the GetterDict class (see utils.py), which attempts to provide a dictionary-like interface to any class. You can customise how this works by setting your own sub-class of `GetterDict` as the value of Config.getter<sub>dict</sub>.
You can also customise class validation using root<sub>validators</sub> with `pre=True`. In this case your validator function will be passed a GetterDict instance which you may copy and modify.
<a id="org9e8b38a"></a>
[SQLModel](https://sqlmodel.tiangolo.com/) is a library for interacting with SQL databases from Python code, with Python objects.
When working with pure SQL, it's common to name the tables in plural. So, the table would be named heroes instead of hero, because it could contain multiple rows, each with one hero. And it's common practice to use singular names for classes (e.g. class Hero, instead of class Heroes). Using singular names for classes like class Hero also makes your code more intuitive. It is possible to override the table name.
<a id="orgd97e7bb"></a>
## Create DB Connection
from sqlmodel import createengine engine = createengine("sqlite:///database.db")
SQLModel.metadata.createall(engine)
<a id="orga528d30"></a>
## Create SQLModel
from typing import Optional from sqlmodel import Field, SQLModel
class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primarykey=True) name: str secretname: str age: Optional[int] = None
<a id="orgdec2792"></a>
## Create Data
from sqlmodel import Session
hero1 = Hero(name="Rusty", secretname="Mr Bean")
with Session(engine) as session: session.add(hero1) session.commit()
<a id="orgbc8500f"></a>
## Select
from sqlmodel import select
with Session(engine) as session: statement = select(Hero).where(Hero.name == "Spider-Boy") hero = session.exec(statement).first() print(hero)
To ask for user input and then do an SQL query based on that you can do:
userid = input("Type the user ID: ")
session.exec( select(Hero).where(Hero.id == userid) ).all()
<a id="org284e60a"></a>
<a id="orgceff815"></a>
## mypy
https://docs.pydantic.dev/usage/mypy/
<a id="org23d4756"></a>
## devtools
https://docs.pydantic.dev/usage/devtools/
<a id="orge05d12d"></a>
## rich
Dataclasses
src/conf.py
An example of structuring configurations with pydantic dataclasses.
"""Configuration module for hax."""
import keyring
import platform
from datetime import date, datetime, timedelta
from hax.dev import BrewPackage
from tomli import load
from pathlib import Path
from pydantic import BaseModel, SecretStr, computed_field
from pydantic.dataclasses import dataclass
def get_config(file: Path) -> dict:
"""Read a file in TOML format and return a dict."""
with open(file, mode="rb") as f:
config = load(f)
return config
@dataclass
class Project:
"""Project class which contains project related settings.
Attributes:
name: The name of the project.
version: The version of the project.
path: The path to the project. Is a computed value based on which operating system is used.
"""
os = platform.system()
path: Path = Path.home() / "Repos" / "bojort" if os == "Darwin" else Path.home() / "hax"
database_dir = Path.home() / "Databases"
temp_dir: Path = Path.home() / "temp"
config_file: Path = path / "pyproject.toml"
playwright_auth: Path = temp_dir / "playwright_auth.json"
name: str = get_config(file=config_file)["project"]["name"]
version: str = get_config(file=config_file)["project"]["version"]
@dataclass
class Deps:
"""Dependencies class which contains dependencies related settings.
Attributes:
az_cli_version: The version of Azure CLI.
"""
az_cli_version: str = BrewPackage(package_name="az").version()
@dataclass
class Bojort:
"""Bojort class which contains Bojort related settings."""
path: Path = Path.home() / "Repos" / "bojort"
config_file: Path = path / "pyproject.toml"
name: str = get_config(file=config_file)["project"]["name"]
version: str = get_config(file=config_file)["project"]["version"]
@dataclass
class Crosskey:
"""Crosskey class which contains Crosskey related settings.
Attributes:
ad_username: The username for Crosskey AD.
jira_url: The url for Crosskey Jira.
wiki_url: The url for Crosskey Confluence.
"""
ad_username: str = "kgronber"
jira_url: str = "https://jira.crosskey.fi"
wiki_url: str = "https://wiki.crosskey.fi"
class Secret(BaseModel):
"""Class which contains secrets."""
@computed_field()
def crosskey_ad_password(self) -> SecretStr:
password = keyring.get_password(
service_name=f"Crosskey AD ({Crosskey.ad_username})", username=Crosskey.ad_username)
return SecretStr(password)
@computed_field()
def openai_token(self) -> SecretStr:
token = keyring.get_password(service_name="OpenAI", username="openai-token")
return SecretStr(token)
@dataclass(frozen=True)
class Time:
"""Class for time related settings."""
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
today = date.today()
yesterday = today - timedelta(days=1)
ty = today.strftime("%Y")
tm = today.strftime("%m")
td = today.strftime("%d")
yy = yesterday.strftime("%Y")
ym = yesterday.strftime("%m")
yd = yesterday.strftime("%d")