From d3492548d395880cbb733530ac806df65c12012d Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 08:47:33 +0300 Subject: [PATCH 1/9] Domain name string type --- pydantic_extra_types/domain.py | 5 +++++ tests/test_domain.py | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 pydantic_extra_types/domain.py create mode 100644 tests/test_domain.py diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py new file mode 100644 index 0000000..ca95fe2 --- /dev/null +++ b/pydantic_extra_types/domain.py @@ -0,0 +1,5 @@ +from pydantic import StringConstraints +from typing import Annotated + +DomainStr = Annotated[str, StringConstraints(strip_whitespace=True, to_lower=True, min_length=1, max_length=253, + pattern=r"^([a-z0-9-]+(\.[a-z0-9-]+)+)$")] \ No newline at end of file diff --git a/tests/test_domain.py b/tests/test_domain.py new file mode 100644 index 0000000..f7d74f9 --- /dev/null +++ b/tests/test_domain.py @@ -0,0 +1,35 @@ +from pydantic_extra_types.domain import DomainStr +from pydantic import BaseModel, ValidationError +import pytest + +class MyModel(BaseModel): + domain: DomainStr + +very_long_domains_list = [ + "sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com", + "a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net" +] + +@pytest.mark.parametrize('domain', ["a.com", "x.com"]) +def test_single_letter_domain(domain: str): + MyModel.model_validate({"domain": domain}) + + +@pytest.mark.parametrize('domain', very_long_domains_list) +def test_domains_over_253_characters(domain: str): + assert len(domain) > 253 + try: + MyModel.model_validate({"domain": domain}) + raise Exception("Domain did not throw an error for having over 253 characters") + except ValidationError: + # An error is expected on this test + pass + +@pytest.mark.parametrize('domain', [""]) +def test_domains_over_253_characters(domain: str): + try: + MyModel.model_validate({"domain": domain}) + raise Exception("Domain did not throw an error for having less than 1 character") + except ValidationError: + # An error is expected on this test + pass \ No newline at end of file From 33be0f36d96c44d89db00945b266e9a60380a405 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 08:54:46 +0300 Subject: [PATCH 2/9] fix linting issues --- pydantic_extra_types/domain.py | 2 +- tests/test_domain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index ca95fe2..0d83c3e 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -2,4 +2,4 @@ from typing import Annotated DomainStr = Annotated[str, StringConstraints(strip_whitespace=True, to_lower=True, min_length=1, max_length=253, - pattern=r"^([a-z0-9-]+(\.[a-z0-9-]+)+)$")] \ No newline at end of file + pattern=r"^([a-z0-9-]+(\.[a-z0-9-]+)+)$")] diff --git a/tests/test_domain.py b/tests/test_domain.py index f7d74f9..df21ab8 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -32,4 +32,4 @@ def test_domains_over_253_characters(domain: str): raise Exception("Domain did not throw an error for having less than 1 character") except ValidationError: # An error is expected on this test - pass \ No newline at end of file + pass From 75ff7ea99c785e085e2ba15e3998f7f76f517b72 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 08:56:40 +0300 Subject: [PATCH 3/9] fix lint by replacing double quotes with single quotes --- pydantic_extra_types/domain.py | 2 +- tests/test_domain.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index 0d83c3e..037656d 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -2,4 +2,4 @@ from typing import Annotated DomainStr = Annotated[str, StringConstraints(strip_whitespace=True, to_lower=True, min_length=1, max_length=253, - pattern=r"^([a-z0-9-]+(\.[a-z0-9-]+)+)$")] + pattern=r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$')] diff --git a/tests/test_domain.py b/tests/test_domain.py index df21ab8..f50fa02 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -6,30 +6,30 @@ class MyModel(BaseModel): domain: DomainStr very_long_domains_list = [ - "sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com", - "a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net" + 'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com', + 'a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net' ] -@pytest.mark.parametrize('domain', ["a.com", "x.com"]) +@pytest.mark.parametrize('domain', ['a.com', 'x.com']) def test_single_letter_domain(domain: str): - MyModel.model_validate({"domain": domain}) + MyModel.model_validate({'domain': domain}) @pytest.mark.parametrize('domain', very_long_domains_list) def test_domains_over_253_characters(domain: str): assert len(domain) > 253 try: - MyModel.model_validate({"domain": domain}) - raise Exception("Domain did not throw an error for having over 253 characters") + MyModel.model_validate({'domain': domain}) + raise Exception('Domain did not throw an error for having over 253 characters') except ValidationError: # An error is expected on this test pass -@pytest.mark.parametrize('domain', [""]) +@pytest.mark.parametrize('domain', ['']) def test_domains_over_253_characters(domain: str): try: - MyModel.model_validate({"domain": domain}) - raise Exception("Domain did not throw an error for having less than 1 character") + MyModel.model_validate({'domain': domain}) + raise Exception('Domain did not throw an error for having less than 1 character') except ValidationError: # An error is expected on this test pass From af48f0abde1365ad0e69c568dd2aa2121611f679 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 09:06:30 +0300 Subject: [PATCH 4/9] fix linting issues (final) --- pydantic_extra_types/domain.py | 11 ++++++--- tests/test_domain.py | 43 +++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index 037656d..6d9f6cf 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -1,5 +1,10 @@ -from pydantic import StringConstraints from typing import Annotated -DomainStr = Annotated[str, StringConstraints(strip_whitespace=True, to_lower=True, min_length=1, max_length=253, - pattern=r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$')] +from pydantic import StringConstraints + +DomainStr = Annotated[ + str, + StringConstraints( + strip_whitespace=True, to_lower=True, min_length=1, max_length=253, pattern=r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$' + ), +] diff --git a/tests/test_domain.py b/tests/test_domain.py index f50fa02..63be1f8 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -1,15 +1,19 @@ -from pydantic_extra_types.domain import DomainStr -from pydantic import BaseModel, ValidationError import pytest +from pydantic import BaseModel, ValidationError + +from pydantic_extra_types.domain import DomainStr + class MyModel(BaseModel): - domain: DomainStr + domain: DomainStr + very_long_domains_list = [ - 'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com', - 'a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net' + 'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com', + 'a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net', ] + @pytest.mark.parametrize('domain', ['a.com', 'x.com']) def test_single_letter_domain(domain: str): MyModel.model_validate({'domain': domain}) @@ -17,19 +21,20 @@ def test_single_letter_domain(domain: str): @pytest.mark.parametrize('domain', very_long_domains_list) def test_domains_over_253_characters(domain: str): - assert len(domain) > 253 - try: - MyModel.model_validate({'domain': domain}) - raise Exception('Domain did not throw an error for having over 253 characters') - except ValidationError: - # An error is expected on this test - pass + assert len(domain) > 253 + try: + MyModel.model_validate({'domain': domain}) + raise Exception('Domain did not throw an error for having over 253 characters') + except ValidationError: + # An error is expected on this test + pass + @pytest.mark.parametrize('domain', ['']) -def test_domains_over_253_characters(domain: str): - try: - MyModel.model_validate({'domain': domain}) - raise Exception('Domain did not throw an error for having less than 1 character') - except ValidationError: - # An error is expected on this test - pass +def test_domains_having_less_than_one_character(domain: str): + try: + MyModel.model_validate({'domain': domain}) + raise Exception('Domain did not throw an error for having less than 1 character') + except ValidationError: + # An error is expected on this test + pass From f97968b1efe2d0f2fb4c113322de115bc08704bb Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 09:08:52 +0300 Subject: [PATCH 5/9] add support for python 3.8 --- pydantic_extra_types/domain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index 6d9f6cf..a12c36f 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -1,4 +1,8 @@ -from typing import Annotated +try: + from typing import Annotated +except ImportError: + # Python 3.8 + from typing_extensions import Annotated from pydantic import StringConstraints From 6e5d7842fc0130b60322e994d659c34b1acca1b1 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 09:12:51 +0300 Subject: [PATCH 6/9] fix incompatible imports --- pydantic_extra_types/domain.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index a12c36f..f8541cb 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -1,8 +1,9 @@ -try: - from typing import Annotated -except ImportError: - # Python 3.8 - from typing_extensions import Annotated +import sys + +if sys.version_info < (3, 9): # pragma: no cover + from typing_extensions import Annotated # pragma: no cover +else: + from typing import Annotated # pragma: no cover from pydantic import StringConstraints From 7274819512aa4f7ab684e48e87325d94cd14c1b2 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 13:04:44 +0300 Subject: [PATCH 7/9] add proposed changes and test cases --- pydantic_extra_types/domain.py | 59 +++++++++++++++++++++++++++------- tests/test_domain.py | 55 ++++++++++++++++++++++++------- tests/test_json_schema.py | 31 +++++++++++------- 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index f8541cb..4559946 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -1,15 +1,50 @@ -import sys +""" +The `domain_str` module provides the `DomainStr` data type. +This class depends on the `pydantic` package and implements custom validation for domain string format. +""" -if sys.version_info < (3, 9): # pragma: no cover - from typing_extensions import Annotated # pragma: no cover -else: - from typing import Annotated # pragma: no cover +from __future__ import annotations -from pydantic import StringConstraints +import re +from typing import Any, Generator, Mapping -DomainStr = Annotated[ - str, - StringConstraints( - strip_whitespace=True, to_lower=True, min_length=1, max_length=253, pattern=r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$' - ), -] +from pydantic import GetCoreSchemaHandler +from pydantic_core import PydanticCustomError, core_schema + + +class DomainStr(str): + """ + A string subclass with custom validation for domain string format. + """ + + @classmethod + def __get_validators__(cls) -> Generator[Any]: + yield cls.validate + + @classmethod + def validate(cls, v: Any) -> DomainStr: + if not isinstance(v, str): + raise PydanticCustomError('domain_type', 'Value must be a string') + + v = v.strip().lower() + if len(v) < 1 or len(v) > 253: + raise PydanticCustomError('domain_length', 'Domain must be between 1 and 253 characters') + + pattern = r'^([a-z0-9-]+(\.[a-z0-9-]+)+)$' + if not re.match(pattern, v): + raise PydanticCustomError('domain_format', 'Invalid domain format') + + return cls(v) + + @classmethod + def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + return core_schema.no_info_after_validator_function( + cls.validate, + core_schema.str_schema(), + ) + + @classmethod + def __get_pydantic_json_schema__( + cls, schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler + ) -> Mapping[str, Any]: + return handler(schema) diff --git a/tests/test_domain.py b/tests/test_domain.py index 63be1f8..d967f26 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -8,33 +8,64 @@ class MyModel(BaseModel): domain: DomainStr -very_long_domains_list = [ +valid_domains = [ + 'example.com', + 'sub.example.com', + 'sub-domain.example-site.co.uk', + 'a.com', + 'x.com', + '1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.com', # Multiple subdomains +] + +invalid_domains = [ + '', # Empty string + 'example', # Missing TLD + '.com', # Missing domain name + 'example.', # Trailing dot + 'exam ple.com', # Space in domain + 'exa_mple.com', # Underscore in domain + 'example.com.', # Trailing dot +] + +very_long_domains = [ + 'a' * 249 + '.com', # Just under the limit + 'a' * 250 + '.com', # At the limit + 'a' * 251 + '.com', # Just over the limit 'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com', - 'a-very-very-long-subdomain-name-that-continues-forever-and-ever-testing-the-length-limit-of-domains-to-make-sure-it-reaches-beyond-the-maximum-allowed-for-experimentation-and-testing-purposes.extremely-extended-domain-name-example-for-253-characters-limit.net', ] -@pytest.mark.parametrize('domain', ['a.com', 'x.com']) -def test_single_letter_domain(domain: str): - MyModel.model_validate({'domain': domain}) +@pytest.mark.parametrize('domain', valid_domains) +def test_valid_domains(domain: str): + """ + Tests + """ + try: + MyModel.model_validate({'domain': domain}) + assert len(domain) < 254 and len(domain) > 0 + except ValidationError: + assert len(domain) > 254 or len(domain) == 0 -@pytest.mark.parametrize('domain', very_long_domains_list) -def test_domains_over_253_characters(domain: str): - assert len(domain) > 253 +@pytest.mark.parametrize('domain', invalid_domains) +def test_invalid_domains(domain: str): try: + print(len(domain)) MyModel.model_validate({'domain': domain}) - raise Exception('Domain did not throw an error for having over 253 characters') + raise Exception( + f"This test case has only samples that should raise a ValidationError. This domain '{domain}' did not raise such an exception." + ) except ValidationError: # An error is expected on this test pass -@pytest.mark.parametrize('domain', ['']) -def test_domains_having_less_than_one_character(domain: str): +@pytest.mark.parametrize('domain', very_long_domains) +def test_very_long_domains(domain: str): try: + print(len(domain)) MyModel.model_validate({'domain': domain}) - raise Exception('Domain did not throw an error for having less than 1 character') + assert len(domain) < 254 and len(domain) > 0 except ValidationError: # An error is expected on this test pass diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index 86bdc57..efeade3 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -13,20 +13,11 @@ import pydantic_extra_types from pydantic_extra_types.color import Color from pydantic_extra_types.coordinate import Coordinate, Latitude, Longitude -from pydantic_extra_types.country import ( - CountryAlpha2, - CountryAlpha3, - CountryNumericCode, - CountryShortName, -) +from pydantic_extra_types.country import CountryAlpha2, CountryAlpha3, CountryNumericCode, CountryShortName from pydantic_extra_types.currency_code import ISO4217, Currency +from pydantic_extra_types.domain import DomainStr from pydantic_extra_types.isbn import ISBN -from pydantic_extra_types.language_code import ( - ISO639_3, - ISO639_5, - LanguageAlpha2, - LanguageName, -) +from pydantic_extra_types.language_code import ISO639_3, ISO639_5, LanguageAlpha2, LanguageName from pydantic_extra_types.mac_address import MacAddress from pydantic_extra_types.payment import PaymentCardNumber from pydantic_extra_types.pendulum_dt import DateTime @@ -451,6 +442,22 @@ ], }, ), + ( + DomainStr, + { + 'title': 'Model', + 'type': 'object', + 'properties': { + 'x': { + 'title': 'X', + 'type': 'string', + }, + }, + 'required': [ + 'x', + ], + }, + ), ], ) def test_json_schema(cls, expected): From 66916f9588cd8771008d371d1e02f2181751dc57 Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 13:41:55 +0300 Subject: [PATCH 8/9] add 100% coverage and remove prints from tests --- pydantic_extra_types/domain.py | 6 +++--- tests/test_domain.py | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index 4559946..73969fc 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -19,12 +19,12 @@ class DomainStr(str): @classmethod def __get_validators__(cls) -> Generator[Any]: - yield cls.validate + yield cls.validate # pragma: no cover @classmethod def validate(cls, v: Any) -> DomainStr: if not isinstance(v, str): - raise PydanticCustomError('domain_type', 'Value must be a string') + raise PydanticCustomError('domain_type', 'Value must be a string') # pragma: no cover v = v.strip().lower() if len(v) < 1 or len(v) > 253: @@ -47,4 +47,4 @@ def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHa def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler ) -> Mapping[str, Any]: - return handler(schema) + return handler(schema) # pragma: no cover diff --git a/tests/test_domain.py b/tests/test_domain.py index d967f26..00ed401 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -1,3 +1,5 @@ +from typing import Any + import pytest from pydantic import BaseModel, ValidationError @@ -34,12 +36,11 @@ class MyModel(BaseModel): 'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com', ] +invalid_domain_types = [1, 2, 1.1, 2.1, False, [], {}, None] + @pytest.mark.parametrize('domain', valid_domains) def test_valid_domains(domain: str): - """ - Tests - """ try: MyModel.model_validate({'domain': domain}) assert len(domain) < 254 and len(domain) > 0 @@ -50,7 +51,6 @@ def test_valid_domains(domain: str): @pytest.mark.parametrize('domain', invalid_domains) def test_invalid_domains(domain: str): try: - print(len(domain)) MyModel.model_validate({'domain': domain}) raise Exception( f"This test case has only samples that should raise a ValidationError. This domain '{domain}' did not raise such an exception." @@ -63,9 +63,20 @@ def test_invalid_domains(domain: str): @pytest.mark.parametrize('domain', very_long_domains) def test_very_long_domains(domain: str): try: - print(len(domain)) MyModel.model_validate({'domain': domain}) assert len(domain) < 254 and len(domain) > 0 except ValidationError: # An error is expected on this test pass + + +@pytest.mark.parametrize('domain', invalid_domain_types) +def test_invalid_domain_types(domain: Any): + try: + MyModel.model_validate({'domain': domain}) + raise Exception( + f"This test case types that should raise the ValidationError exception. This value '{domain}' did not raise such an exception." + ) + except ValidationError: + # An error is expected on this test + pass From 816dcc45285bb1b7fe0ba3410b9a075c02c3067e Mon Sep 17 00:00:00 2001 From: matter1-git Date: Fri, 6 Sep 2024 15:06:25 +0300 Subject: [PATCH 9/9] add proper type validation for DomainStr and type tests --- pydantic_extra_types/domain.py | 25 ++++++++++++++++++------- tests/test_domain.py | 10 ++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pydantic_extra_types/domain.py b/pydantic_extra_types/domain.py index 73969fc..916cd70 100644 --- a/pydantic_extra_types/domain.py +++ b/pydantic_extra_types/domain.py @@ -6,7 +6,7 @@ from __future__ import annotations import re -from typing import Any, Generator, Mapping +from typing import Any, Mapping from pydantic import GetCoreSchemaHandler from pydantic_core import PydanticCustomError, core_schema @@ -18,13 +18,24 @@ class DomainStr(str): """ @classmethod - def __get_validators__(cls) -> Generator[Any]: - yield cls.validate # pragma: no cover + def validate(cls, __input_value: Any, _: Any) -> str: + """ + Validate a domain name from the provided value. + + Args: + __input_value: The value to be validated. + _: The source type to be converted. + + Returns: + str: The parsed domain name. + + """ + return cls._validate(__input_value) @classmethod - def validate(cls, v: Any) -> DomainStr: + def _validate(cls, v: Any) -> DomainStr: if not isinstance(v, str): - raise PydanticCustomError('domain_type', 'Value must be a string') # pragma: no cover + raise PydanticCustomError('domain_type', 'Value must be a string') v = v.strip().lower() if len(v) < 1 or len(v) > 253: @@ -38,7 +49,7 @@ def validate(cls, v: Any) -> DomainStr: @classmethod def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - return core_schema.no_info_after_validator_function( + return core_schema.with_info_before_validator_function( cls.validate, core_schema.str_schema(), ) @@ -47,4 +58,4 @@ def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHa def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler ) -> Mapping[str, Any]: - return handler(schema) # pragma: no cover + return handler(schema) diff --git a/tests/test_domain.py b/tests/test_domain.py index 00ed401..a149b94 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -72,11 +72,5 @@ def test_very_long_domains(domain: str): @pytest.mark.parametrize('domain', invalid_domain_types) def test_invalid_domain_types(domain: Any): - try: - MyModel.model_validate({'domain': domain}) - raise Exception( - f"This test case types that should raise the ValidationError exception. This value '{domain}' did not raise such an exception." - ) - except ValidationError: - # An error is expected on this test - pass + with pytest.raises(ValidationError, match='Value must be a string'): + MyModel(domain=domain)