Skip to content

Commit

Permalink
Index improvements (#6314)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Jun 30, 2023
1 parent 89032e1 commit 08c473a
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 115 deletions.
4 changes: 3 additions & 1 deletion docs/errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ def convert_errors(
custom_message = custom_messages.get(error['type'])
if custom_message:
ctx = error.get('ctx')
error['msg'] = custom_message.format(**ctx) if ctx else custom_message
error['msg'] = (
custom_message.format(**ctx) if ctx else custom_message
)
new_errors.append(error)
return new_errors

Expand Down
1 change: 1 addition & 0 deletions docs/extra/tweaks.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ aside.blog img {
[data-md-color-scheme="slate"] #company-grid {
background-color: #ffffff;
border-radius: .5rem;
color: black;
}

.tile {
Expand Down
140 changes: 82 additions & 58 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,116 +7,140 @@

{{ version }}.

Pydantic is the most widely used Python library for data validation and coercion using Python type annotations.
Pydantic is the most widely used data validation library for Python.

!!! success
Already using Pydantic V1? See the [Migration Guide](migration.md) for notes on upgrading to Pydantic V2 in your applications!

## Why use Pydantic?

Built-in Python type annotations are a useful way to hint about the expected type of data, improving code clarity and supporting development tools. However, Python's type annotations are optional and don't affect the runtime behavior of the program.

Static type checkers like [mypy](https://mypy-lang.org/) use type annotations to catch potential type-related errors before running the program. But static type checkers can't catch all errors, and they don't affect the runtime behavior of the program.

Pydantic, on the other hand, uses type annotations to perform [data validation](usage/validators.md) and [type coercion](usage/conversion_table.md) at runtime, which is particularly useful for ensuring the correctness of user or external data.

Pydantic enables you to convert input data to Python [standard library types and custom types](usage/types/types.md) in a controlled manner, ensuring they meet the specifications you've provided. This eliminates a significant amount of manual data validation and transformation code, making your program more robust and less prone to errors. It's particularly helpful when dealing with untrusted user input such as form data, [JSON documents](usage/json_schema.md), and other data types.

By providing a simple, declarative way of defining how data should be shaped, Pydantic helps you write cleaner, safer, and more reliable code.

## Features of Pydantic
```py lint="skip" upgrade="skip" title="Pydantic Example" requires="3.10"
from pydantic import BaseModel

Some of the main features of Pydantic include:
class MyModel(BaseModel):
a: int
b: list[str]

- [**Data validation**](usage/validators.md): Pydantic validates data as it is assigned to ensure it meets the requirements. It automatically handles a broad range of data types, including custom types and custom validators.
- [**Standard library and custom data types**](usage/types/types.md): Pydantic supports all of the Python standard library types, and you can define custom data types and specify how they should be validated and converted.
- [**Conversion types**](usage/conversion_table.md): Pydantic will not only validate data, but also convert it to the appropriate type if possible. For instance, a string containing a number will be converted to the proper numerical type.
- [**Custom and nested models**](usage/models.md): You can define models (similar to classes) that contain other models, allowing for complex data structures to be neatly and efficiently represented.
- [**Generic models**](usage/models.md#generic-models): Pydantic supports generic models, which allow the declaration of models that are "parameterized" on one or more fields.
- [**Dataclasses**](usage/dataclasses.md): Pydantic supports `dataclasses.dataclass`, offering same data validation as using `BaseModel`.
- [**Model schema generation**](usage/json_schema.md): Pydantic models can be converted to and from a JSON Schema, which can be useful for documentation, code generation, or other purposes.
- [**Error handling**](errors/errors.md): Pydantic models raise informative errors when invalid data is provided, with the option to create your own [custom errors](errors/errors.md#custom-errors).
- [**Settings management**](api/pydantic_settings.md): The `BaseSettings` class from [pydantic-settings](https://github.com/pydantic/pydantic-settings) provides a way to validate, document, and provide default values for environment variables.
m = MyModel(a=123, b=['a', 'b', 'c'])
print(m.model_dump())
#> {'a': 123, 'b': ['a', 'b', 'c']}
```

Pydantic is simple to use, even when doing complex things, and enables you to define and validate data in pure, canonical Python.
## Why use Pydantic?

[Installing Pydantic](install.md) is as simple as: [`pip install pydantic`](install.md).
- **Powered by type hints** — with Pydantic, schema validation and serialization are controlled by type annotations; less to learn, less code to write and integration with your IDE and static analysis tools.
- **Speed** — Pydantic's core validation logic is written in Rust, as a result Pydantic is among the fastest data validation libraries for Python.
- **JSON Schema** — Pydantic models can emit JSON Schema allowing for easy integration with other tools.
- **Strict** and **Lax** mode — Pydantic can run in either `strict=True` mode (where data is not converted) or `strict=False` mode where Pydantic tries to coerce data to the correct type where appropriate.
- **Dataclasses**, **TypedDicts** and more — Pydantic supports validation of many standard library types including `dataclass` and `TypedDict`.
- **Customisation** — Pydantic allows custom validators and serializers to alter how data is processed in many powerful ways.
- **Ecosystem** — around 8,000 packages on PyPI use Pydantic, including massively popular libraries like
[FastAPI](https://github.com/tiangolo/fastapi),
[huggingface/transformers](https://github.com/huggingface/transformers),
[Django Ninja](https://github.com/vitalik/django-ninja),
[SQLModel](https://github.com/tiangolo/sqlmodel),
and [LangChain](https://github.com/hwchase17/langchain).
- **Battle tested** — Pydantic is downloaded >70m times/month and is used by all FAANG companies and 20 of the 25 largest companies on NASDAQ — if you're trying to do something with Pydantic, someone else has probably already done it.

[Installing Pydantic](install.md) is as simple as: [`pip install pydantic`](install.md)

## Pydantic examples

To see Pydantic at work, let's start with a simple example, creating a custom class that inherits from `BaseModel`:

```py
```py upgrade="skip" title="Validation Successful" requires="3.10"
from datetime import datetime
from typing import Optional

from pydantic import BaseModel
from pydantic import BaseModel, PositiveInt


class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: Optional[datetime] = None
id: int # (1)!
name: str = 'John Doe' # (2)!
signup_ts: datetime | None # (3)!
tastes: dict[str, PositiveInt] # (4)!


external_data = {
'id': '123',
'signup_ts': '2019-06-01 12:22',
'id': 123,
'signup_ts': '2019-06-01 12:22', # (5)!
'tastes': {
'wine': 9,
b'cheese': 7, # (6)!
'cabbage': '1', # (7)!
},
}

user = User(**external_data)

print(user.model_dump())
#> {'id': 123, 'name': 'John Doe', 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22)}
user = User(**external_data) # (8)!

print(user.id) # (9)!
#> 123
print(user.model_dump()) # (10)!
"""
{
'id': 123,
'name': 'John Doe',
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1},
}
"""
```

What's going on here:

* `id` is of type `int`; the annotation-only declaration tells Pydantic that this field is required. Strings,
1. `id` is of type `int`; the annotation-only declaration tells Pydantic that this field is required. Strings,
bytes, or floats will be coerced to ints if possible; otherwise an exception will be raised.
* `name` is inferred as a string from the provided default; because it has a default, it is not required.
* `signup_ts` is a `datetime` field that is not required (and takes the value `None` if a value is not supplied).
2. `name` is a string; because it has a default, it is not required.
3. `signup_ts` is a `datetime` field that is required, but the value `None` may be provided;
Pydantic will process either a unix timestamp int (e.g. `1496498400`) or a string representing the date and time.
4. `tastes` is a dictionary with string keys and positive integer values. The `PositiveInt` type is shorthand for `Annotated[int, annotated_types.Gt(0)]`.
5. The input here is an ISO8601 formatted datetime, Pydantic will convert it to a `datetime` object.
6. The key here is `bytes`, but Pydantic will take care of coercing it to a string.
7. Similarly, Pydantic will coerce the string `'1'` to an integer `1`.
8. Here we create instance of `User` by passing our external data to `User` as keyword arguments
9. We can access fields as attributes of the model
10. We can convert the model to a dictionary with `model_dump()`

If validation fails, Pydantic will raise an error with a breakdown of what was wrong:

```py
```py upgrade="skip" title="Validation Error" requires="3.10"
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ValidationError
from pydantic import BaseModel, PositiveInt, ValidationError


class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: Optional[datetime] = None
signup_ts: datetime | None
tastes: dict[str, PositiveInt]


external_data = {'id': 'not an int', 'tastes': {}} # (1)!

try:
User(name=1234)
User(**external_data) # (2)!
except ValidationError as e:
print(e.errors())
"""
[
{
'type': 'missing',
'type': 'int_parsing',
'loc': ('id',),
'msg': 'Field required',
'input': {'name': 1234},
'url': 'https://errors.pydantic.dev/2/v/missing',
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not an int',
'url': 'https://errors.pydantic.dev/2/v/int_parsing',
},
{
'type': 'string_type',
'loc': ('name',),
'msg': 'Input should be a valid string',
'input': 1234,
'url': 'https://errors.pydantic.dev/2/v/string_type',
'type': 'missing',
'loc': ('signup_ts',),
'msg': 'Field required',
'input': {'id': 'not an int', 'tastes': {}},
'url': 'https://errors.pydantic.dev/2/v/missing',
},
]
"""
```

1. the input data is wrong here — `id` is a valid int, and `signup_ts` is missing
2. `User(...)` will raise a `ValidationError` with a list of errors

## Who is using Pydantic?

Hundreds of organisations and packages are using Pydantic. Some of the prominent companies and organizations around the world who are using Pydantic include:
Expand Down Expand Up @@ -294,7 +318,7 @@ for (const company of companies) {
const tile = document.createElement('div');
tile.classList.add('tile');
tile.innerHTML = `
<img src="${company.logoUrl}" />
<img src="${company.logoUrl}" alt="${company.name}" />
`;
grid.appendChild(tile);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ from pydantic import BaseModel, ValidationError

class Foo(BaseModel):
f1: str # required, cannot be None
f2: Optional[str] # required, can be None - same as Union[str, None] / str | None
f2: Optional[str] # required, can be None - same as str | None
f3: Optional[str] = None # not required, can be None
f4: str = 'Foobar' # not required, but cannot be None

Expand Down
17 changes: 13 additions & 4 deletions docs/usage/dataclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class User:

user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
#> User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""
User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""
```

!!! note
Expand Down Expand Up @@ -63,7 +65,11 @@ print(TypeAdapter(User).json_schema())
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {'default': 'John Doe', 'title': 'Name', 'type': 'string'},
'friends': {'items': {'type': 'integer'}, 'title': 'Friends', 'type': 'array'},
'friends': {
'items': {'type': 'integer'},
'title': 'Friends',
'type': 'array',
},
'age': {
'anyOf': [{'type': 'integer'}, {'type': 'null'}],
'default': None,
Expand Down Expand Up @@ -230,7 +236,9 @@ file = File(
last_modification_time='2020-01-01T00:00',
) # nothing is validated as expected
print(file)
#> File(filename=['not', 'a', 'string'], last_modification_time='2020-01-01T00:00')
"""
File(filename=['not', 'a', 'string'], last_modification_time='2020-01-01T00:00')
"""

try:
Foo(file=file)
Expand Down Expand Up @@ -286,8 +294,9 @@ try:
dc: DC
other: str

# invalid as it is now a pydantic dataclass
Model(dc=my_dc, other='other')
except PydanticSchemaGenerationError as e: # invalid as it is now a pydantic dataclass
except PydanticSchemaGenerationError as e:
print(e.message)
"""
Unable to generate pydantic-core schema for <class '__main__.ArbitraryType'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
Expand Down
8 changes: 6 additions & 2 deletions docs/usage/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ class Model(BaseModel):


print(Model.model_json_schema())
#> {'examples': 'examples', 'properties': {}, 'title': 'Model', 'type': 'object'}
"""
{'examples': 'examples', 'properties': {}, 'title': 'Model', 'type': 'object'}
"""
```

## Decorator on missing field {#decorator-missing-field}
Expand Down Expand Up @@ -201,7 +203,9 @@ from pydantic import AliasChoices, BaseModel, Field, PydanticUserError


class Cat(BaseModel):
pet_type: Literal['cat'] = Field(validation_alias=AliasChoices('Pet', 'PET'))
pet_type: Literal['cat'] = Field(
validation_alias=AliasChoices('Pet', 'PET')
)
c: str


Expand Down
14 changes: 11 additions & 3 deletions docs/usage/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ You can read more about [Alias Precedence](model_config.md#alias-precedence) in

class MyModel(BaseModel):
my_field: int = Field(
..., alias='myValidationAlias', serialization_alias='my_serialization_alias'
...,
alias='myValidationAlias',
serialization_alias='my_serialization_alias',
)


Expand Down Expand Up @@ -321,7 +323,9 @@ foo = Foo(
love_for_pydantic=float('inf'),
)
print(foo)
#> positive=1 non_negative=0 negative=-1 non_positive=0 even=2 love_for_pydantic=inf
"""
positive=1 non_negative=0 negative=-1 non_positive=0 even=2 love_for_pydantic=inf
"""
```

??? info "JSON Schema"
Expand Down Expand Up @@ -702,7 +706,11 @@ print(User.model_json_schema())
"""
{
'properties': {
'age': {'description': 'Age of the user', 'title': 'Age', 'type': 'integer'},
'age': {
'description': 'Age of the user',
'title': 'Age',
'type': 'integer',
},
'email': {
'examples': ['marcelo@mail.com'],
'format': 'email',
Expand Down
20 changes: 15 additions & 5 deletions docs/usage/json_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,9 @@ class CompressedString:
cls._validate,
core_schema.str_schema(),
serialization=core_schema.plain_serializer_function_ser_schema(
cls._serialize, info_arg=False, return_schema=core_schema.str_schema()
cls._serialize,
info_arg=False,
return_schema=core_schema.str_schema(),
),
)

Expand All @@ -422,7 +424,9 @@ class CompressedString:
if word not in inverse_dictionary:
inverse_dictionary[word] = len(inverse_dictionary)
text.append(inverse_dictionary[word])
return CompressedString({v: k for k, v in inverse_dictionary.items()}, text)
return CompressedString(
{v: k for k, v in inverse_dictionary.items()}, text
)

@staticmethod
def _serialize(value: 'CompressedString') -> str:
Expand All @@ -443,7 +447,9 @@ print(MyModel.model_json_schema())
}
"""
print(MyModel(value='fox fox fox dog fox'))
#> value=CompressedString(dictionary={0: 'fox', 1: 'dog'}, text=[0, 0, 0, 1, 0])
"""
value = CompressedString(dictionary={0: 'fox', 1: 'dog'}, text=[0, 0, 0, 1, 0])
"""

print(MyModel(value='fox fox fox dog fox').model_dump(mode='json'))
#> {'value': 'fox fox fox dog fox'}
Expand Down Expand Up @@ -472,7 +478,9 @@ class RestrictCharacters:
) -> core_schema.CoreSchema:
if not self.alphabet:
raise ValueError('Alphabet may not be empty')
schema = handler(source) # get the CoreSchema from the type / inner constraints
schema = handler(
source
) # get the CoreSchema from the type / inner constraints
if schema['type'] != 'str':
raise TypeError('RestrictCharacters can only be applied to strings')
return core_schema.no_info_after_validator_function(
Expand All @@ -482,7 +490,9 @@ class RestrictCharacters:

def validate(self, value: str) -> str:
if any(c not in self.alphabet for c in value):
raise ValueError(f'{value!r} is not restricted to {self.alphabet!r}')
raise ValueError(
f'{value!r} is not restricted to {self.alphabet!r}'
)
return value


Expand Down
Loading

0 comments on commit 08c473a

Please sign in to comment.