From cad4e2131e52e17541e5f221181a059bbc61cbbc Mon Sep 17 00:00:00 2001 From: Zidane Date: Fri, 17 Nov 2023 12:35:39 +0530 Subject: [PATCH 1/4] added UTCDateTime constrained type --- pydantic/__init__.py | 2 ++ pydantic/types.py | 21 +++++++++++++++++++ tests/mypy/modules/success.py | 2 ++ .../outputs/1.0.1/mypy-default_ini/success.py | 2 ++ .../1.0.1/pyproject-default_toml/success.py | 2 ++ tests/test_datetime.py | 7 +++++++ tests/test_types.py | 3 +++ 7 files changed, 39 insertions(+) diff --git a/pydantic/__init__.py b/pydantic/__init__.py index d7e90a53a5..4479957710 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -171,6 +171,7 @@ 'PastDatetime', 'FutureDatetime', 'AwareDatetime', + 'UTCDatetime', 'NaiveDatetime', 'AllowInfNan', 'EncoderProtocol', @@ -314,6 +315,7 @@ 'PastDatetime': (__package__, '.types'), 'FutureDatetime': (__package__, '.types'), 'AwareDatetime': (__package__, '.types'), + 'UTCDatetime': (__package__, '.types'), 'NaiveDatetime': (__package__, '.types'), 'AllowInfNan': (__package__, '.types'), 'EncoderProtocol': (__package__, '.types'), diff --git a/pydantic/types.py b/pydantic/types.py index d371852457..6bcdfc662d 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -89,6 +89,7 @@ 'FutureDatetime', 'condate', 'AwareDatetime', + 'UTCDatetime', 'NaiveDatetime', 'AllowInfNan', 'EncoderProtocol', @@ -1890,6 +1891,7 @@ def condate( if TYPE_CHECKING: AwareDatetime = Annotated[datetime, ...] + UTCDatetime = Annotated[datetime, ...] NaiveDatetime = Annotated[datetime, ...] PastDatetime = Annotated[datetime, ...] FutureDatetime = Annotated[datetime, ...] @@ -1915,6 +1917,25 @@ def __get_pydantic_core_schema__( def __repr__(self) -> str: return 'AwareDatetime' + class UTCDatetime: + """A datetime that requires timezone info.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.datetime_schema(tz_constraint=0) + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'datetime', cls.__name__) + schema['tz_constraint'] = 0 + return schema + + def __repr__(self) -> str: + return 'UTCDatetime' + class NaiveDatetime: """A datetime that doesn't require timezone info.""" diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py index d68f2f2256..2f549fd646 100644 --- a/tests/mypy/modules/success.py +++ b/tests/mypy/modules/success.py @@ -39,6 +39,7 @@ StrictInt, StrictStr, UrlConstraints, + UTCDatetime, WrapValidator, create_model, field_validator, @@ -242,6 +243,7 @@ class PydanticTypes(BaseModel): my_past_datetime: PastDatetime = datetime.now() - timedelta(1) my_future_datetime: FutureDatetime = datetime.now() + timedelta(1) my_aware_datetime: AwareDatetime = datetime.now(tz=timezone.utc) + my_utc_datetime: UTCDatetime = datetime.now(tz=timezone.utc) my_naive_datetime: NaiveDatetime = datetime.now() diff --git a/tests/mypy/outputs/1.0.1/mypy-default_ini/success.py b/tests/mypy/outputs/1.0.1/mypy-default_ini/success.py index ceda32fe49..2713851e02 100644 --- a/tests/mypy/outputs/1.0.1/mypy-default_ini/success.py +++ b/tests/mypy/outputs/1.0.1/mypy-default_ini/success.py @@ -14,6 +14,7 @@ from pydantic import ( UUID1, AwareDatetime, + UTCDatetime, BaseModel, ConfigDict, DirectoryPath, @@ -248,6 +249,7 @@ class PydanticTypes(BaseModel): my_past_datetime: PastDatetime = datetime.now() - timedelta(1) my_future_datetime: FutureDatetime = datetime.now() + timedelta(1) my_aware_datetime: AwareDatetime = datetime.now(tz=timezone.utc) + my_utc_datetime: UTCDatetime = datetime.now(tz=timezone.utc) my_naive_datetime: NaiveDatetime = datetime.now() diff --git a/tests/mypy/outputs/1.0.1/pyproject-default_toml/success.py b/tests/mypy/outputs/1.0.1/pyproject-default_toml/success.py index ceda32fe49..2713851e02 100644 --- a/tests/mypy/outputs/1.0.1/pyproject-default_toml/success.py +++ b/tests/mypy/outputs/1.0.1/pyproject-default_toml/success.py @@ -14,6 +14,7 @@ from pydantic import ( UUID1, AwareDatetime, + UTCDatetime, BaseModel, ConfigDict, DirectoryPath, @@ -248,6 +249,7 @@ class PydanticTypes(BaseModel): my_past_datetime: PastDatetime = datetime.now() - timedelta(1) my_future_datetime: FutureDatetime = datetime.now() + timedelta(1) my_aware_datetime: AwareDatetime = datetime.now(tz=timezone.utc) + my_utc_datetime: UTCDatetime = datetime.now(tz=timezone.utc) my_naive_datetime: NaiveDatetime = datetime.now() diff --git a/tests/test_datetime.py b/tests/test_datetime.py index f96c894050..95585670c7 100644 --- a/tests/test_datetime.py +++ b/tests/test_datetime.py @@ -13,6 +13,7 @@ NaiveDatetime, PastDate, PastDatetime, + UTCDatetime, ValidationError, condate, ) @@ -49,6 +50,11 @@ def aware_datetime_type(request): return request.param +@pytest.fixture(scope='module', params=[UTCDatetime, Annotated[datetime, UTCDatetime()]]) +def utc_datetime_type(request): + return request.param + + @pytest.fixture(scope='module', params=[NaiveDatetime, Annotated[datetime, NaiveDatetime()]]) def naive_datetime_type(request): return request.param @@ -588,6 +594,7 @@ class Model(BaseModel): FutureDatetime, NaiveDatetime, AwareDatetime, + UTCDatetime, ), ) def test_invalid_annotated_type(annotation): diff --git a/tests/test_types.py b/tests/test_types.py index 2bfaa4a5b9..86331e1edb 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -92,6 +92,7 @@ StrictStr, Tag, TypeAdapter, + UTCDatetime, ValidationError, conbytes, condate, @@ -4095,6 +4096,7 @@ class Foobar(BaseModel): PastDatetime, FutureDatetime, AwareDatetime, + UTCDatetime, NaiveDatetime, ], ) @@ -5884,6 +5886,7 @@ def test_annotated_default_value_functional_validator() -> None: (PastDate, 'PastDate'), (FutureDate, 'FutureDate'), (AwareDatetime, 'AwareDatetime'), + (UTCDatetime, 'UTCDatetime'), (NaiveDatetime, 'NaiveDatetime'), (PastDatetime, 'PastDatetime'), (FutureDatetime, 'FutureDatetime'), From dc8eb35d5342024c40a58ccd4db600b1422d5d5b Mon Sep 17 00:00:00 2001 From: Zidane Date: Fri, 17 Nov 2023 14:48:24 +0530 Subject: [PATCH 2/4] added docstring for UTCDatetime --- pydantic/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/types.py b/pydantic/types.py index 6bcdfc662d..02fd7ba52a 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -1918,7 +1918,7 @@ def __repr__(self) -> str: return 'AwareDatetime' class UTCDatetime: - """A datetime that requires timezone info.""" + """A datetime that needs UTC as the timezone.""" @classmethod def __get_pydantic_core_schema__( From 8934ec118f3b4763b20eadd0dd4ed4644d553f33 Mon Sep 17 00:00:00 2001 From: Zidane Date: Fri, 17 Nov 2023 15:29:52 +0530 Subject: [PATCH 3/4] updated docstring for UTCDatetime --- pydantic/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/types.py b/pydantic/types.py index 02fd7ba52a..be2fb4a07f 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -1918,7 +1918,7 @@ def __repr__(self) -> str: return 'AwareDatetime' class UTCDatetime: - """A datetime that needs UTC as the timezone.""" + """A datetime that needs UTC as the timezone.UTC timezone offset is 0""" @classmethod def __get_pydantic_core_schema__( From e86f96462c66beadb13f4c9f999788bb5e687a7c Mon Sep 17 00:00:00 2001 From: Zidane Date: Fri, 17 Nov 2023 16:03:57 +0530 Subject: [PATCH 4/4] updated docstring for UTCDatetime --- pydantic/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/types.py b/pydantic/types.py index be2fb4a07f..02fd7ba52a 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -1918,7 +1918,7 @@ def __repr__(self) -> str: return 'AwareDatetime' class UTCDatetime: - """A datetime that needs UTC as the timezone.UTC timezone offset is 0""" + """A datetime that needs UTC as the timezone.""" @classmethod def __get_pydantic_core_schema__(