Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a with_config decorator to comply with typing spec #8611

Merged
merged 4 commits into from Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion pydantic/__init__.py
Expand Up @@ -19,7 +19,7 @@
from ._internal._generate_schema import GenerateSchema as GenerateSchema
from .aliases import AliasChoices, AliasGenerator, AliasPath
from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
from .config import ConfigDict
from .config import ConfigDict, with_config
from .errors import *
from .fields import Field, PrivateAttr, computed_field
from .functional_serializers import (
Expand Down Expand Up @@ -80,6 +80,7 @@
'WrapSerializer',
# config
'ConfigDict',
'with_config',
# deprecated V1 config, these are imported via `__getattr__` below
'BaseConfig',
'Extra',
Expand Down Expand Up @@ -234,6 +235,7 @@
'WrapSerializer': (__package__, '.functional_serializers'),
# config
'ConfigDict': (__package__, '.config'),
'with_config': (__package__, '.config'),
# validate call
'validate_call': (__package__, '.validate_call_decorator'),
# errors
Expand Down
17 changes: 15 additions & 2 deletions pydantic/config.py
@@ -1,7 +1,7 @@
"""Configuration for Pydantic models."""
from __future__ import annotations as _annotations

from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, TypeVar, Union

from typing_extensions import Literal, TypeAlias, TypedDict

Expand All @@ -11,7 +11,7 @@
if TYPE_CHECKING:
from ._internal._generate_schema import GenerateSchema as _GenerateSchema

__all__ = ('ConfigDict',)
__all__ = ('ConfigDict', 'with_config')


JsonValue: TypeAlias = Union[int, float, str, bool, None, List['JsonValue'], 'JsonDict']
Expand Down Expand Up @@ -909,4 +909,17 @@ class Model(BaseModel):
"""


_TypeT = TypeVar('_TypeT', bound=type)


def with_config(config: ConfigDict) -> Callable[[_TypeT], _TypeT]:
"""A convenience decorator to set a Pydantic configuration on a `TypedDict`."""

def inner(typed_dict: _TypeT, /) -> _TypeT:
setattr(typed_dict, '__pydantic_config__', config)
return typed_dict

return inner


__getattr__ = getattr_migration(__name__)
11 changes: 11 additions & 0 deletions tests/test_types_typeddict.py
Expand Up @@ -20,6 +20,7 @@
PositiveInt,
PydanticUserError,
ValidationError,
with_config,
)
from pydantic._internal._decorators import get_attribute_from_bases
from pydantic.functional_serializers import field_serializer, model_serializer
Expand Down Expand Up @@ -919,3 +920,13 @@ class C(B):
pass

assert get_attribute_from_bases(C, 'x') == 2


def test_typeddict_with_config_decorator():
@with_config(ConfigDict(str_to_lower=True))
class Model(TypedDict):
x: str

ta = TypeAdapter(Model)

assert ta.validate_python({'x': 'ABC'}) == {'x': 'abc'}