diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c66bfd05..b1941d08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }}-latest steps: diff --git a/pydantic_extra_types/color.py b/pydantic_extra_types/color.py index 8010df87..5229adbc 100644 --- a/pydantic_extra_types/color.py +++ b/pydantic_extra_types/color.py @@ -11,14 +11,8 @@ import math import re -import sys from colorsys import hls_to_rgb, rgb_to_hls -from typing import Any, Callable, Tuple, Union, cast - -if sys.version_info >= (3, 8): # pragma: no cover - from typing import Literal -else: # pragma: no cover - from typing_extensions import Literal +from typing import Any, Callable, Literal, Tuple, Union, cast from pydantic import GetJsonSchemaHandler from pydantic._internal import _repr diff --git a/pydantic_extra_types/country.py b/pydantic_extra_types/country.py index a32fa60a..a6d26e2e 100644 --- a/pydantic_extra_types/country.py +++ b/pydantic_extra_types/country.py @@ -11,7 +11,7 @@ from pydantic_core import PydanticCustomError, core_schema try: - import pycountry # type: ignore[import] + import pycountry except ModuleNotFoundError: # pragma: no cover raise RuntimeError( 'The `country` module requires "pycountry" to be installed. You can install it with "pip install pycountry".' @@ -24,11 +24,9 @@ class CountryInfo: alpha3: str numeric_code: str short_name: str - # NOTE: Not all countries have an official name - official_name: str -@lru_cache() +@lru_cache def _countries() -> list[CountryInfo]: return [ CountryInfo( @@ -36,37 +34,31 @@ def _countries() -> list[CountryInfo]: alpha3=country.alpha_3, numeric_code=country.numeric, short_name=country.name, - official_name=getattr(country, 'official_name', ''), ) for country in pycountry.countries ] -@lru_cache() +@lru_cache def _index_by_alpha2() -> dict[str, CountryInfo]: return {country.alpha2: country for country in _countries()} -@lru_cache() +@lru_cache def _index_by_alpha3() -> dict[str, CountryInfo]: return {country.alpha3: country for country in _countries()} -@lru_cache() +@lru_cache def _index_by_numeric_code() -> dict[str, CountryInfo]: return {country.numeric_code: country for country in _countries()} -@lru_cache() +@lru_cache def _index_by_short_name() -> dict[str, CountryInfo]: return {country.short_name: country for country in _countries()} -@lru_cache() -def _index_by_official_name() -> dict[str, CountryInfo]: - return {country.official_name: country for country in _countries() if country.official_name} - - class CountryAlpha2(str): """CountryAlpha2 parses country codes in the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) format. @@ -123,12 +115,6 @@ def short_name(self) -> str: """The country short name.""" return _index_by_alpha2()[self].short_name - @property - def official_name(self) -> str: - """The country official name.""" - country = _index_by_alpha2()[self] - return country.official_name - class CountryAlpha3(str): """CountryAlpha3 parses country codes in the [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) @@ -187,12 +173,6 @@ def short_name(self) -> str: """The country short name.""" return _index_by_alpha3()[self].short_name - @property - def official_name(self) -> str: - """The country official name.""" - country = _index_by_alpha3()[self] - return country.official_name - class CountryNumericCode(str): """CountryNumericCode parses country codes in the @@ -251,12 +231,6 @@ def short_name(self) -> str: """The country short name.""" return _index_by_numeric_code()[self].short_name - @property - def official_name(self) -> str: - """The country official name.""" - country = _index_by_numeric_code()[self] - return country.official_name - class CountryShortName(str): """CountryShortName parses country codes in the short name format. @@ -305,63 +279,3 @@ def alpha3(self) -> str: def numeric_code(self) -> str: """The country code in the [ISO 3166-1 numeric](https://en.wikipedia.org/wiki/ISO_3166-1_numeric) format.""" return _index_by_short_name()[self].numeric_code - - @property - def official_name(self) -> str: - """The country official name.""" - country = _index_by_short_name()[self] - return country.official_name - - -class CountryOfficialName(str): - """CountryOfficialName parses country codes in the official name format. - - ```py - from pydantic import BaseModel - - from pydantic_extra_types.country import CountryOfficialName - - class Product(BaseModel): - made_in: CountryOfficialName - - product = Product(made_in="United States of America") - print(product) - #> made_in='United States of America' - ``` - """ - - @classmethod - def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryOfficialName: - if __input_value not in _index_by_official_name(): - raise PydanticCustomError('country_numeric_code', 'Invalid country official name') - return cls(__input_value) - - @classmethod - def __get_pydantic_core_schema__( - cls, source: type[Any], handler: GetCoreSchemaHandler - ) -> core_schema.AfterValidatorFunctionSchema: - return core_schema.with_info_after_validator_function( - cls._validate, - core_schema.str_schema(), - serialization=core_schema.to_string_ser_schema(), - ) - - @property - def alpha2(self) -> str: - """The country code in the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) format.""" - return _index_by_official_name()[self].alpha2 - - @property - def alpha3(self) -> str: - """The country code in the [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) format.""" - return _index_by_official_name()[self].alpha3 - - @property - def numeric_code(self) -> str: - """The country code in the [ISO 3166-1 numeric](https://en.wikipedia.org/wiki/ISO_3166-1_numeric) format.""" - return _index_by_official_name()[self].numeric_code - - @property - def short_name(self) -> str: - """The country short name.""" - return _index_by_official_name()[self].short_name diff --git a/pyproject.toml b/pyproject.toml index 8a7e998c..cc51c1ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,11 +19,11 @@ classifiers = [ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: System Administrators', @@ -37,7 +37,7 @@ classifiers = [ 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Internet', ] -requires-python = '>=3.7' +requires-python = '>=3.8' dependencies = [ 'pydantic>=2.5.2', ] @@ -46,7 +46,7 @@ dynamic = ['version'] [project.optional-dependencies] all = [ 'phonenumbers>=8,<9', - 'pycountry>=22,<23', + 'pycountry>=23,<24', 'python-ulid>=1,<2', ] diff --git a/requirements/linting.txt b/requirements/linting.txt index 14686a38..de749627 100644 --- a/requirements/linting.txt +++ b/requirements/linting.txt @@ -2,23 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --output-file=requirements/linting.txt --resolver=backtracking requirements/linting.in +# pip-compile --no-emit-index-url --output-file=requirements/linting.txt requirements/linting.in # -annotated-types==0.5.0 +annotated-types==0.6.0 # via -r requirements/linting.in -black==23.3.0 +black==23.12.1 # via -r requirements/linting.in -cfgv==3.3.1 +cfgv==3.4.0 # via pre-commit -click==8.1.3 +click==8.1.7 # via black -distlib==0.3.6 +distlib==0.3.8 # via virtualenv -filelock==3.12.0 +filelock==3.13.1 # via virtualenv -identify==2.5.24 +identify==2.5.33 # via pre-commit -mypy==1.3.0 +mypy==1.8.0 # via -r requirements/linting.in mypy-extensions==1.0.0 # via @@ -26,27 +26,27 @@ mypy-extensions==1.0.0 # mypy nodeenv==1.8.0 # via pre-commit -packaging==23.1 +packaging==23.2 # via black -pathspec==0.11.1 +pathspec==0.12.1 # via black -platformdirs==3.5.1 +platformdirs==4.1.0 # via # black # virtualenv -pre-commit==3.3.2 +pre-commit==3.6.0 # via -r requirements/linting.in -pyupgrade==3.4.0 +pyupgrade==3.15.0 # via -r requirements/linting.in -pyyaml==6.0 +pyyaml==6.0.1 # via pre-commit -ruff==0.0.270 +ruff==0.1.11 # via -r requirements/linting.in -tokenize-rt==5.0.0 +tokenize-rt==5.2.0 # via pyupgrade -typing-extensions==4.6.3 +typing-extensions==4.9.0 # via mypy -virtualenv==20.23.0 +virtualenv==20.25.0 # via pre-commit # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/pyproject.txt b/requirements/pyproject.txt index 7f70390d..2b7d35c8 100644 --- a/requirements/pyproject.txt +++ b/requirements/pyproject.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --extra=all --output-file=requirements/pyproject.txt --resolver=backtracking pyproject.toml +# pip-compile --extra=all --no-emit-index-url --output-file=requirements/pyproject.txt pyproject.toml # -annotated-types==0.5.0 +annotated-types==0.6.0 # via pydantic -phonenumbers==8.13.13 +phonenumbers==8.13.27 # via pydantic-extra-types (pyproject.toml) -pycountry==22.3.5 +pycountry==23.12.11 # via pydantic-extra-types (pyproject.toml) -pydantic==2.5.2 +pydantic==2.5.3 # via pydantic-extra-types (pyproject.toml) -pydantic-core==2.14.5 +pydantic-core==2.14.6 # via pydantic python-ulid==1.1.0 # via pydantic-extra-types (pyproject.toml) -typing-extensions==4.6.3 +typing-extensions==4.9.0 # via # pydantic # pydantic-core diff --git a/requirements/testing.txt b/requirements/testing.txt index 02cda3be..9d38f1f9 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -2,36 +2,36 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --output-file=requirements/testing.txt --resolver=backtracking requirements/testing.in +# pip-compile --no-emit-index-url --output-file=requirements/testing.txt requirements/testing.in # -certifi==2023.5.7 +certifi==2023.11.17 # via requests -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests codecov==2.1.13 # via -r requirements/testing.in -coverage[toml]==7.2.7 +coverage[toml]==7.4.0 # via # -r requirements/testing.in # codecov # pytest-cov -dirty-equals==0.6.0 +dirty-equals==0.7.1.post0 # via -r requirements/testing.in -idna==3.4 +idna==3.6 # via requests iniconfig==2.0.0 # via pytest -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==23.1 +packaging==23.2 # via pytest -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -pygments==2.15.1 +pygments==2.17.2 # via rich -pytest==7.3.1 +pytest==7.4.4 # via # -r requirements/testing.in # pytest-cov @@ -40,11 +40,11 @@ pytest-cov==4.1.0 # via -r requirements/testing.in pytest-pretty==1.2.0 # via -r requirements/testing.in -pytz==2023.3 +pytz==2023.3.post1 # via dirty-equals requests==2.31.0 # via codecov -rich==13.4.1 +rich==13.7.0 # via pytest-pretty -urllib3==2.0.2 +urllib3==2.1.0 # via requests diff --git a/tests/test_country_code.py b/tests/test_country_code.py index 686b7f7c..13bb85a0 100644 --- a/tests/test_country_code.py +++ b/tests/test_country_code.py @@ -8,12 +8,10 @@ CountryAlpha3, CountryInfo, CountryNumericCode, - CountryOfficialName, CountryShortName, _index_by_alpha2, _index_by_alpha3, _index_by_numeric_code, - _index_by_official_name, _index_by_short_name, ) @@ -52,25 +50,13 @@ class Product(BaseModel): return Product -@pytest.fixture(scope='module', name='ProductOfficialName') -def product_official_name_fixture(): - class Product(BaseModel): - made_in: CountryOfficialName - - return Product - - -@pytest.mark.parametrize( - 'alpha2, country_data', - [(alpha2, country_data) for alpha2, country_data in _index_by_alpha2().items()][:PARAMS_AMOUNT], -) +@pytest.mark.parametrize('alpha2, country_data', list(_index_by_alpha2().items())[:PARAMS_AMOUNT]) def test_valid_alpha2(alpha2: str, country_data: CountryInfo, ProductAlpha2): banana = ProductAlpha2(made_in=alpha2) assert banana.made_in == country_data.alpha2 assert banana.made_in.alpha3 == country_data.alpha3 assert banana.made_in.numeric_code == country_data.numeric_code assert banana.made_in.short_name == country_data.short_name - assert banana.made_in.official_name == country_data.official_name @pytest.mark.parametrize('alpha2', list(printable)) @@ -79,17 +65,13 @@ def test_invalid_alpha2(alpha2: str, ProductAlpha2): ProductAlpha2(made_in=alpha2) -@pytest.mark.parametrize( - 'alpha3, country_data', - [(alpha3, country_data) for alpha3, country_data in _index_by_alpha3().items()][:PARAMS_AMOUNT], -) +@pytest.mark.parametrize('alpha3, country_data', list(_index_by_alpha3().items())[:PARAMS_AMOUNT]) def test_valid_alpha3(alpha3: str, country_data: CountryInfo, ProductAlpha3): banana = ProductAlpha3(made_in=alpha3) assert banana.made_in == country_data.alpha3 assert banana.made_in.alpha2 == country_data.alpha2 assert banana.made_in.numeric_code == country_data.numeric_code assert banana.made_in.short_name == country_data.short_name - assert banana.made_in.official_name == country_data.official_name @pytest.mark.parametrize('alpha3', list(printable)) @@ -98,17 +80,13 @@ def test_invalid_alpha3(alpha3: str, ProductAlpha3): ProductAlpha3(made_in=alpha3) -@pytest.mark.parametrize( - 'short_name, country_data', - [(short_name, country_data) for short_name, country_data in _index_by_short_name().items()][:PARAMS_AMOUNT], -) +@pytest.mark.parametrize('short_name, country_data', list(_index_by_short_name().items())[:PARAMS_AMOUNT]) def test_valid_short_name(short_name: str, country_data: CountryInfo, ProductShortName): banana = ProductShortName(made_in=short_name) assert banana.made_in == country_data.short_name assert banana.made_in.alpha2 == country_data.alpha2 assert banana.made_in.alpha3 == country_data.alpha3 assert banana.made_in.numeric_code == country_data.numeric_code - assert banana.made_in.official_name == country_data.official_name @pytest.mark.parametrize('short_name', list(printable)) @@ -117,38 +95,13 @@ def test_invalid_short_name(short_name: str, ProductShortName): ProductShortName(made_in=short_name) -@pytest.mark.parametrize( - 'official_name, country_data', - [(official_name, country_data) for official_name, country_data in _index_by_official_name().items()][ - :PARAMS_AMOUNT - ], -) -def test_valid_official_name(official_name: str, country_data: CountryInfo, ProductOfficialName): - banana = ProductOfficialName(made_in=official_name) - assert banana.made_in == country_data.official_name - assert banana.made_in.alpha2 == country_data.alpha2 - assert banana.made_in.alpha3 == country_data.alpha3 - assert banana.made_in.short_name == country_data.short_name - assert banana.made_in.numeric_code == country_data.numeric_code - - -@pytest.mark.parametrize('official_name', list(printable)) -def test_invalid_official_name(official_name: str, ProductOfficialName): - with pytest.raises(ValidationError, match='Invalid country official name'): - ProductOfficialName(made_in=official_name) - - -@pytest.mark.parametrize( - 'numeric_code, country_data', - [(numeric_code, country_data) for numeric_code, country_data in _index_by_numeric_code().items()][:PARAMS_AMOUNT], -) +@pytest.mark.parametrize('numeric_code, country_data', list(_index_by_numeric_code().items())[:PARAMS_AMOUNT]) def test_valid_numeric_code(numeric_code: str, country_data: CountryInfo, ProductNumericCode): banana = ProductNumericCode(made_in=numeric_code) assert banana.made_in == country_data.numeric_code assert banana.made_in.alpha2 == country_data.alpha2 assert banana.made_in.alpha3 == country_data.alpha3 assert banana.made_in.short_name == country_data.short_name - assert banana.made_in.official_name == country_data.official_name @pytest.mark.parametrize('numeric_code', list(printable)) diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index ab38f67e..8508e87a 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -7,7 +7,6 @@ CountryAlpha2, CountryAlpha3, CountryNumericCode, - CountryOfficialName, CountryShortName, ) from pydantic_extra_types.isbn import ISBN @@ -71,15 +70,6 @@ 'type': 'object', }, ), - ( - CountryOfficialName, - { - 'properties': {'x': {'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', - }, - ), ( CountryShortName, {