summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 02:48:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 02:48:08 +0000
commite6b0b20305ffdd91cc0330cf73233f0172891ebc (patch)
tree92b41ba8113b4fe462474506be44c18bb64b8c2e
parentReleasing debian version 2.6.0-2. (diff)
downloadpydantic-extra-types-e6b0b20305ffdd91cc0330cf73233f0172891ebc.tar.xz
pydantic-extra-types-e6b0b20305ffdd91cc0330cf73233f0172891ebc.zip
Merging upstream version 2.7.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--HISTORY.md175
-rw-r--r--Makefile4
-rw-r--r--pydantic_extra_types/__init__.py2
-rw-r--r--pydantic_extra_types/color.py1
-rw-r--r--pydantic_extra_types/coordinate.py1
-rw-r--r--pydantic_extra_types/country.py1
-rw-r--r--pydantic_extra_types/currency_code.py1
-rw-r--r--pydantic_extra_types/language_code.py212
-rw-r--r--pydantic_extra_types/pendulum_dt.py118
-rw-r--r--pydantic_extra_types/phone_numbers.py1
-rw-r--r--pydantic_extra_types/routing_number.py1
-rw-r--r--pydantic_extra_types/ulid.py1
-rw-r--r--requirements/linting.txt6
-rw-r--r--requirements/testing.txt6
-rw-r--r--tests/test_json_schema.py20
-rw-r--r--tests/test_language_codes.py55
-rw-r--r--tests/test_pendulum_dt.py93
-rw-r--r--tests/test_ulid.py4
18 files changed, 603 insertions, 99 deletions
diff --git a/HISTORY.md b/HISTORY.md
index 430800c..c3f4864 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,82 +1,97 @@
# CHANGELOG
-## v2.6.0
-
-* Allow python-ulid 2.x on Python 3.9 and later by @musicinmybrain in <https://github.com/pydantic/pydantic-extra-types/pull/131>
-* Do not pin the ”major” version of pycountry by @musicinmybrain in <https://github.com/pydantic/pydantic-extra-types/pull/132>
-* πŸ€– Create dependabot.yml for updating GitHub action by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/134>
-* :memo: Refactor Documentation for ISBN and MAC address modules by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/124>
-* Add language code definitions and test by @07pepa in <https://github.com/pydantic/pydantic-extra-types/pull/141>
-* :memo: Create a `changelog` to match release notes by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/142>
-* Add currency code ISO 4217 and its subset that includes only currencies by @07pepa in <https://github.com/pydantic/pydantic-extra-types/pull/143>
-* πŸ”¨ Update code formatting and linting configurations by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/144>
-* πŸ‘· Add Python checking for dependencies by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/145>
-* πŸ› fix single quote issue by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/148>
-
-## v2.5.0
-
-* Add Pendulum DT support by @theunkn0wn1 in <https://github.com/pydantic/pydantic-extra-types/pull/110>
-
-## v2.4.1
-
-* Fix refs blocking docs build by @sydney-runkle in <https://github.com/pydantic/pydantic-extra-types/pull/125>
-
-## v2.4.0
-
-* Add: New type ISBN by @lucasmucidas in <https://github.com/pydantic/pydantic-extra-types/pull/116>
-* fix validate_digits actually allowing non digit characters by @romaincaillon in <https://github.com/pydantic/pydantic-extra-types/pull/120>
-* ♻️ refactor the `validate_brand` method & add new types by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/56>
-* βœ… Drop python 3.7 & support 3.12 by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/122>
-
-## v2.3.0
-
-* Upgrade pydantic version to >=2.5.2 by @hramezani in <https://github.com/pydantic/pydantic-extra-types/pull/113>
-
-## v.2.2.0
-
-* Add `long` and `short` format to `as_hex` by @DJRHails in <https://github.com/pydantic/pydantic-extra-types/pull/93>
-* Refactor documentation by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/98>
-* ✨ add `ULID` type by @JeanArhancet in <https://github.com/pydantic/pydantic-extra-types/pull/73>
-* Added `__get_pydantic_json_schema__` method with `format='tel'` by @hasansezertasan in <https://github.com/pydantic/pydantic-extra-types/pull/106>
-
-## v2.1.0
-
-* ✨ add `MacAddress` type by @JeanArhancet in <https://github.com/pydantic/pydantic-extra-types/pull/71>
-* :memo: fix usage of `MAC address` by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/72>
-* Add docstrings for payment cards by @tpdorsey in <https://github.com/pydantic/pydantic-extra-types/pull/77>
-* Fix mac adddress validation by @JeanArhancet in <https://github.com/pydantic/pydantic-extra-types/pull/79>
-* Remove work in progress part from README.md by @hramezani in <https://github.com/pydantic/pydantic-extra-types/pull/81>
-* Add `Latitude`, `Longitude` and `Coordinate` by @JeanArhancet in <https://github.com/pydantic/pydantic-extra-types/pull/76>
-* Refactor: use stdlib and remove useless code by @eumiro in <https://github.com/pydantic/pydantic-extra-types/pull/86>
-* Make Latitude and Longitude evaluated by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/90>
-
-## v2.0.0
-
-* Migrate `Color` & `Payment Card` by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/2>
-* add `pydantic` to classifiers by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/13>
-* remove dependencies caching by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/16>
-* :bug: deprecate `__modify_schema__` method by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/20>
-* Fix Color JSON schema generation by @dmontagu in <https://github.com/pydantic/pydantic-extra-types/pull/21>
-* fix issues of `pydantic_core.core_schema` has no attribute `xxx` by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/23>
-* Fix Failed tests for `color` type by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/26>
-* Created Country type by @HomiGrotas in <https://github.com/pydantic/pydantic-extra-types/pull/14>
-* Add phone number types by @JamesHutchison in <https://github.com/pydantic/pydantic-extra-types/pull/25>
-* make `phonenumbers` a requirement by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/29>
-* chore(feat): Add ABARouting number type by @RevinderDev in <https://github.com/pydantic/pydantic-extra-types/pull/30>
-* add missing countries by @EssaAlshammri in <https://github.com/pydantic/pydantic-extra-types/pull/32>
-* chore: resolve `pydantic-core` dependency conflict by @hirotasoshu in <https://github.com/pydantic/pydantic-extra-types/pull/45>
-* Add `MIR` card brand by @hirotasoshu in <https://github.com/pydantic/pydantic-extra-types/pull/46>
-* fix dependencies version by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/48>
-* πŸ“ Add documentation for `Color` and `PaymentCardNumber` by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/50>
-* Add hooky by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/51>
-* ♻️ Simplify project structure by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/52>
-* πŸ‘· Add coverage check on the pipeline by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/53>
-* ♻️ refactor country type using `pycountry` by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/54>
-* βœ… Add 100% coverage by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/57>
-* Add support for transparent Color by @CollinHeist in <https://github.com/pydantic/pydantic-extra-types/pull/59>
-* πŸ“ Add documentation for `PhoneNumber` and `ABARoutingNumber` by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/60>
-* πŸ“ Refactor README by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/61>
-* 🚚 Rename `routing_number.md` to `routing_numbers.md` by @Kludex in <https://github.com/pydantic/pydantic-extra-types/pull/62>
-* :memo: fix code in `payment` documentation by @yezz123 in <https://github.com/pydantic/pydantic-extra-types/pull/63>
-* uprev pydantic to b3 by @samuelcolvin in <https://github.com/pydantic/pydantic-extra-types/pull/69>
-* Prepare for release 2.0.0 by @hramezani in <https://github.com/pydantic/pydantic-extra-types/pull/70>
+## Latest Changes
+
+## 2.7.0
+
+* πŸ”₯ Remove latest-changes workflow. PR [#165](https://github.com/pydantic/pydantic-extra-types/pull/165) by [yezz123](https://github.com/yezz123)
+* πŸ”¨ Add latest-changes workflow to generate Changes. PR [#164](https://github.com/pydantic/pydantic-extra-types/pull/164) by [yezz123](https://github.com/yezz123)
+* Added LanguageAlpha2 and LanguageName types. PR [#153](https://github.com/pydantic/pydantic-extra-types/pull/153) by [odelmarcelle](https://github.com/odelmarcelle)
+* Added support for pendulum Dates. PR [#154](https://github.com/pydantic/pydantic-extra-types/pull/154) by [Woody1193](https://github.com/Woody1193)
+* Add support for pendulum Duration. PR [#162](https://github.com/pydantic/pydantic-extra-types/pull/162) by [tempookian](https://github.com/tempookian)
+
+### Dependencies
+
+* ⬆ Bump the python-packages group with 1 update. PR [#150](https://github.com/pydantic/pydantic-extra-types/pull/150) by [dependabot](https://github.com/dependabot)
+* ⬆ Bump the python-packages group with 6 updates. PR [#160](https://github.com/pydantic/pydantic-extra-types/pull/160) by [dependabot](https://github.com/dependabot)
+
+## 2.6.0
+
+* Allow python-ulid 2.x on Python 3.9 and later. PR [#131](https://github.com/pydantic/pydantic-extra-types/pull/131) by [@musicinmybrain](https://github.com/musicinmybrain)
+* Do not pin the ”major” version of pycountry. PR [#132](https://github.com/pydantic/pydantic-extra-types/pull/132) by [@musicinmybrain](https://github.com/musicinmybrain)
+* πŸ€– Create dependabot.yml for updating GitHub action. PR [#134](https://github.com/pydantic/pydantic-extra-types/pull/134) by [@yezz123](https://github.com/yezz123)
+* Refactor Documentation for ISBN and MAC address modules. PR [#124](https://github.com/pydantic/pydantic-extra-types/pull/124) by [@yezz123](https://github.com/yezz123)
+* Add language code definitions and test. PR [#141](https://github.com/pydantic/pydantic-extra-types/pull/141) by [@07pepa](https://github.com/07pepa)
+* Create a `changelog` to match release notes. PR [#142](https://github.com/pydantic/pydantic-extra-types/pull/142) by [@yezz123](https://github.com/yezz123)
+* Add currency code ISO 4217 and its subset that includes only currencies. PR [#143](https://github.com/pydantic/pydantic-extra-types/pull/143) by [@07pepa](https://github.com/07pepa)
+* πŸ”¨ Update code formatting and linting configurations. PR [#144](https://github.com/pydantic/pydantic-extra-types/pull/144) by [@yezz123](https://github.com/yezz123)
+* πŸ‘· Add Python checking for dependencies. PR [#145](https://github.com/pydantic/pydantic-extra-types/pull/145) by [@yezz123](https://github.com/yezz123)
+* πŸ› Fix single quote issue. PR [#148](https://github.com/pydantic/pydantic-extra-types/pull/148) by [@yezz123](https://github.com/yezz123)
+
+## 2.5.0
+
+* Add Pendulum DT support. PR [#110](https://github.com/pydantic/pydantic-extra-types/pull/110) by [@theunkn0wn1](https://github.com/theunkn0wn1)
+
+## 2.4.1
+
+* Fix refs blocking docs build. PR [#125](https://github.com/pydantic/pydantic-extra-types/pull/125) by [@sydney-runkle](https://github.com/sydney-runkle)
+
+## 2.4.0
+
+* Add: New type ISBN. PR [#116](https://github.com/pydantic/pydantic-extra-types/pull/116) by [lucasmucidas](https://github.com/lucasmucidas)
+* Fix validate_digits actually allowing non-digit characters. PR [#120](https://github.com/pydantic/pydantic-extra-types/pull/120) by [romaincaillon](https://github.com/romaincaillon)
+* Refactor the `validate_brand` method & add new types. PR [#56](https://github.com/pydantic/pydantic-extra-types/pull/56) by [yezz123](https://github.com/yezz123)
+* Drop Python 3.7 & support 3.12. PR [#122](https://github.com/pydantic/pydantic-extra-types/pull/122) by [yezz123](https://github.com/yezz123)
+
+## 2.3.0
+
+* Upgrade pydantic version to >=2.5.2. PR [#113](https://github.com/pydantic/pydantic-extra-types/pull/113) by [hramezani](https://github.com/hramezani)
+
+## 2.2.0
+
+* Add `long` and `short` format to `as_hex`. PR [#93](https://github.com/pydantic/pydantic-extra-types/pull/93) by [DJRHails](https://github.com/DJRHails)
+* Refactor documentation. PR [#98](https://github.com/pydantic/pydantic-extra-types/pull/98) by [Kludex](https://github.com/Kludex)
+* Add `ULID` type. PR [#73](https://github.com/pydantic/pydantic-extra-types/pull/73) by [JeanArhancet](https://github.com/JeanArhancet)
+* Add `__get_pydantic_json_schema__` method with `format='tel'`. PR [#106](https://github.com/pydantic/pydantic-extra-types/pull/106) by [hasansezertasan](https://github.com/hasansezertasan)
+
+## 2.1.0
+
+* Add `MacAddress` type. PR [#71](https://github.com/pydantic/pydantic-extra-types/pull/71) by [JeanArhancet](https://github.com/JeanArhancet)
+* Fix usage of `MAC address`. PR [#72](https://github.com/pydantic/pydantic-extra-types/pull/72) by [yezz123](https://github.com/yezz123)
+* Add docstrings for payment cards. PR [#77](https://github.com/pydantic/pydantic-extra-types/pull/77) by [tpdorsey](https://github.com/tpdorsey)
+* Fix MAC address validation. PR [#79](https://github.com/pydantic/pydantic-extra-types/pull/79) by [JeanArhancet](https://github.com/JeanArhancet)
+* Remove work in progress part from README.md. PR [#81](https://github.com/pydantic/pydantic-extra-types/pull/81) by [hramezani](https://github.com/hramezani)
+* Add `Latitude`, `Longitude`, and `Coordinate`. PR [#76](https://github.com/pydantic/pydantic-extra-types/pull/76) by [JeanArhancet](https://github.com/JeanArhancet)
+* Refactor: use stdlib and remove useless code. PR [#86](https://github.com/pydantic/pydantic-extra-types/pull/86) by [eumiro](https://github.com/eumiro)
+* Make Latitude and Longitude evaluated. PR [#90](https://github.com/pydantic/pydantic-extra-types/pull/90) by [Kludex](https://github.com/Kludex)
+
+## 2.0.0
+
+* Migrate `Color` & `Payment Card`. PR [#2](https://github.com/pydantic/pydantic-extra-types/pull/2) by [yezz123](https://github.com/yezz123)
+* Add `pydantic` to classifiers. PR [#13](https://github.com/pydantic/pydantic-extra-types/pull/13) by [yezz123](https://github.com/yezz123)
+* Remove dependencies caching. PR [#16](https://github.com/pydantic/pydantic-extra-types/pull/16) by [yezz123](https://github.com/yezz123)
+* Deprecate `__modify_schema__` method. PR [#20](https://github.com/pydantic/pydantic-extra-types/pull/20) by [yezz123](https://github.com/yezz123)
+* Fix Color JSON schema generation. PR [#21](https://github.com/pydantic/pydantic-extra-types/pull/21) by [dmontagu](https://github.com/dmontagu)
+* Fix issues of `pydantic_core.core_schema` has no attribute `xxx`. PR [#23](https://github.com/pydantic/pydantic-extra-types/pull/23) by [yezz123](https://github.com/yezz123)
+* Fix Failed tests for `color` type. PR [#26](https://github.com/pydantic/pydantic-extra-types/pull/26) by [yezz123](https://github.com/yezz123)
+* Created Country type. PR [#14](https://github.com/pydantic/pydantic-extra-types/pull/14) by [HomiGrotas](https://github.com/HomiGrotas)
+* Add phone number types. PR [#25](https://github.com/pydantic/pydantic-extra-types/pull/25) by [JamesHutchison](https://github.com/JamesHutchison)
+* Make `phonenumbers` a requirement. PR [#29](https://github.com/pydantic/pydantic-extra-types/pull/29) by [yezz123](https://github.com/yezz123)
+* Add ABARouting number type. PR [#30](https://github.com/pydantic/pydantic-extra-types/pull/30) by [RevinderDev](https://github.com/RevinderDev)
+* Add missing countries. PR [#32](https://github.com/pydantic/pydantic-extra-types/pull/32) by [EssaAlshammri](https://github.com/EssaAlshammri)
+* Resolve `pydantic-core` dependency conflict. PR [#45](https://github.com/pydantic/pydantic-extra-types/pull/45) by [hirotasoshu](https://github.com/hirotasoshu)
+* Add `MIR` card brand. PR [#46](https://github.com/pydantic/pydantic-extra-types/pull/46) by [hirotasoshu](https://github.com/hirotasoshu)
+* Fix dependencies version. PR [#48](https://github.com/pydantic/pydantic-extra-types/pull/48) by [yezz123](https://github.com/yezz123)
+* Add documentation for `Color` and `PaymentCardNumber`. PR [#50](https://github.com/pydantic/pydantic-extra-types/pull/50) by [Kludex](https://github.com/Kludex)
+* Add hooky. PR [#51](https://github.com/pydantic/pydantic-extra-types/pull/51) by [Kludex](https://github.com/Kludex)
+* Simplify project structure. PR [#52](https://github.com/pydantic/pydantic-extra-types/pull/52) by [Kludex](https://github.com/Kludex)
+* Add coverage check on the pipeline. PR [#53](https://github.com/pydantic/pydantic-extra-types/pull/53) by [Kludex](https://github.com/Kludex)
+* Refactor country type using `pycountry`. PR [#54](https://github.com/pydantic/pydantic-extra-types/pull/54) by [yezz123](https://github.com/yezz123)
+* Add 100% coverage. PR [#57](https://github.com/pydantic/pydantic-extra-types/pull/57) by [Kludex](https://github.com/Kludex)
+* Add support for transparent Color. PR [#59](https://github.com/pydantic/pydantic-extra-types/pull/59) by [CollinHeist](https://github.com/CollinHeist)
+* Add documentation for `PhoneNumber` and `ABARoutingNumber`. PR [#60](https://github.com/pydantic/pydantic-extra-types/pull/60) by [Kludex](https://github.com/Kludex)
+* Refactor README. PR [#61](https://github.com/pydantic/pydantic-extra-types/pull/61) by [Kludex](https://github.com/Kludex)
+* Rename `routing_number.md` to `routing_numbers.md`. PR [#62](https://github.com/pydantic/pydantic-extra-types/pull/62) by [Kludex](https://github.com/Kludex)
+* Fix code in `payment` documentation. PR [#63](https://github.com/pydantic/pydantic-extra-types/pull/63) by [yezz123](https://github.com/yezz123)
+* Uprev pydantic to b3. PR [#69](https://github.com/pydantic/pydantic-extra-types/pull/69) by [samuelcolvin](https://github.com/samuelcolvin)
+* Prepare for release 2.0.0. PR [#70](https://github.com/pydantic/pydantic-extra-types/pull/70) by [hramezani](https://github.com/hramezani)
diff --git a/Makefile b/Makefile
index f25b84f..f8d968f 100644
--- a/Makefile
+++ b/Makefile
@@ -18,12 +18,12 @@ refresh-lockfiles:
.PHONY: format
format:
- ruff --fix $(sources)
+ ruff check --fix $(sources)
ruff format $(sources)
.PHONY: lint
lint:
- ruff $(sources)
+ ruff check $(sources)
ruff format --check $(sources)
.PHONY: mypy
diff --git a/pydantic_extra_types/__init__.py b/pydantic_extra_types/__init__.py
index f0e5e1e..766ce2d 100644
--- a/pydantic_extra_types/__init__.py
+++ b/pydantic_extra_types/__init__.py
@@ -1 +1 @@
-__version__ = '2.6.0'
+__version__ = '2.7.0'
diff --git a/pydantic_extra_types/color.py b/pydantic_extra_types/color.py
index 34aa441..8b9eabe 100644
--- a/pydantic_extra_types/color.py
+++ b/pydantic_extra_types/color.py
@@ -7,6 +7,7 @@ A few colors have multiple names referring to the sames colors, eg. `grey` and `
In these cases the _last_ color when sorted alphabetically takes preferences,
eg. `Color((0, 255, 255)).as_named() == 'cyan'` because "cyan" comes after "aqua".
"""
+
from __future__ import annotations
import math
diff --git a/pydantic_extra_types/coordinate.py b/pydantic_extra_types/coordinate.py
index df470d5..10eaa05 100644
--- a/pydantic_extra_types/coordinate.py
+++ b/pydantic_extra_types/coordinate.py
@@ -3,6 +3,7 @@ The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_
[`Longitude`][pydantic_extra_types.coordinate.Longitude], and
[`Coordinate`][pydantic_extra_types.coordinate.Coordinate] data types.
"""
+
from dataclasses import dataclass
from typing import Any, ClassVar, Tuple, Type
diff --git a/pydantic_extra_types/country.py b/pydantic_extra_types/country.py
index a6d26e2..7af99c7 100644
--- a/pydantic_extra_types/country.py
+++ b/pydantic_extra_types/country.py
@@ -1,6 +1,7 @@
"""
Country definitions that are based on the [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes).
"""
+
from __future__ import annotations
from dataclasses import dataclass
diff --git a/pydantic_extra_types/currency_code.py b/pydantic_extra_types/currency_code.py
index c19d9bf..fbc0cbd 100644
--- a/pydantic_extra_types/currency_code.py
+++ b/pydantic_extra_types/currency_code.py
@@ -1,6 +1,7 @@
"""
Currency definitions that are based on the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217).
"""
+
from __future__ import annotations
from typing import Any
diff --git a/pydantic_extra_types/language_code.py b/pydantic_extra_types/language_code.py
index 117e877..8c15385 100644
--- a/pydantic_extra_types/language_code.py
+++ b/pydantic_extra_types/language_code.py
@@ -1,9 +1,12 @@
"""
Language definitions that are based on the [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3) & [ISO 639-5](https://en.wikipedia.org/wiki/ISO_639-5).
"""
+
from __future__ import annotations
-from typing import Any
+from dataclasses import dataclass
+from functools import lru_cache
+from typing import Any, Union
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import PydanticCustomError, core_schema
@@ -17,6 +20,213 @@ except ModuleNotFoundError: # pragma: no cover
)
+@dataclass
+class LanguageInfo:
+ """
+ LanguageInfo is a dataclass that contains the language information.
+
+ Args:
+ alpha2: The language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format.
+ alpha3: The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format.
+ name: The language name.
+ """
+
+ alpha2: Union[str, None]
+ alpha3: str
+ name: str
+
+
+@lru_cache
+def _languages() -> list[LanguageInfo]:
+ """
+ Return a list of LanguageInfo objects containing the language information.
+
+ Returns:
+ A list of LanguageInfo objects containing the language information.
+ """
+ return [
+ LanguageInfo(
+ alpha2=getattr(language, 'alpha_2', None),
+ alpha3=language.alpha_3,
+ name=language.name,
+ )
+ for language in pycountry.languages
+ ]
+
+
+@lru_cache
+def _index_by_alpha2() -> dict[str, LanguageInfo]:
+ """
+ Return a dictionary with the language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format as the key and the LanguageInfo object as the value.
+ """
+ return {language.alpha2: language for language in _languages() if language.alpha2 is not None}
+
+
+@lru_cache
+def _index_by_alpha3() -> dict[str, LanguageInfo]:
+ """
+ Return a dictionary with the language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format as the key and the LanguageInfo object as the value.
+ """
+ return {language.alpha3: language for language in _languages()}
+
+
+@lru_cache
+def _index_by_name() -> dict[str, LanguageInfo]:
+ """
+ Return a dictionary with the language name as the key and the LanguageInfo object as the value.
+ """
+ return {language.name: language for language in _languages()}
+
+
+class LanguageAlpha2(str):
+ """LanguageAlpha2 parses languages codes in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1)
+ format.
+
+ ```py
+ from pydantic import BaseModel
+
+ from pydantic_extra_types.language_code import LanguageAlpha2
+
+ class Movie(BaseModel):
+ audio_lang: LanguageAlpha2
+ subtitles_lang: LanguageAlpha2
+
+ movie = Movie(audio_lang='de', subtitles_lang='fr')
+ print(movie)
+ #> audio_lang='de' subtitles_lang='fr'
+ ```
+ """
+
+ @classmethod
+ def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageAlpha2:
+ """
+ Validate a language code in the ISO 639-1 alpha-2 format from the provided str value.
+
+ Args:
+ __input_value: The str value to be validated.
+ _: The Pydantic ValidationInfo.
+
+ Returns:
+ The validated language code in the ISO 639-1 alpha-2 format.
+ """
+ if __input_value not in _index_by_alpha2():
+ raise PydanticCustomError('language_alpha2', 'Invalid language alpha2 code')
+ return cls(__input_value)
+
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls, source: type[Any], handler: GetCoreSchemaHandler
+ ) -> core_schema.AfterValidatorFunctionSchema:
+ """
+ Return a Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation.
+
+ Args:
+ source: The source type.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ A Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation.
+ """
+ return core_schema.with_info_after_validator_function(
+ cls._validate,
+ core_schema.str_schema(to_lower=True),
+ )
+
+ @classmethod
+ def __get_pydantic_json_schema__(
+ cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
+ ) -> dict[str, Any]:
+ """
+ Return a Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation.
+
+ Args:
+ schema: The Pydantic CoreSchema.
+ handler: The handler to get the JSON Schema.
+
+ Returns:
+ A Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation.
+ """
+ json_schema = handler(schema)
+ json_schema.update({'pattern': r'^\w{2}$'})
+ return json_schema
+
+ @property
+ def alpha3(self) -> str:
+ """The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format."""
+ return _index_by_alpha2()[self].alpha3
+
+ @property
+ def name(self) -> str:
+ """The language name."""
+ return _index_by_alpha2()[self].name
+
+
+class LanguageName(str):
+ """LanguageName parses languages names listed in the [ISO 639-3 standard](https://en.wikipedia.org/wiki/ISO_639-3)
+ format.
+
+ ```py
+ from pydantic import BaseModel
+
+ from pydantic_extra_types.language_code import LanguageName
+
+ class Movie(BaseModel):
+ audio_lang: LanguageName
+ subtitles_lang: LanguageName
+
+ movie = Movie(audio_lang='Dutch', subtitles_lang='Mandarin Chinese')
+ print(movie)
+ #> audio_lang='Dutch' subtitles_lang='Mandarin Chinese'
+ ```
+ """
+
+ @classmethod
+ def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageName:
+ """
+ Validate a language name from the provided str value.
+
+ Args:
+ __input_value: The str value to be validated.
+ _: The Pydantic ValidationInfo.
+
+ Returns:
+ The validated language name.
+ """
+ if __input_value not in _index_by_name():
+ raise PydanticCustomError('language_name', 'Invalid language name')
+ return cls(__input_value)
+
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls, source: type[Any], handler: GetCoreSchemaHandler
+ ) -> core_schema.AfterValidatorFunctionSchema:
+ """
+ Return a Pydantic CoreSchema with the language name validation.
+
+ Args:
+ source: The source type.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ A Pydantic CoreSchema with the language name validation.
+ """
+ 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) -> Union[str, None]:
+ """The language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format. Does not exist for all languages."""
+ return _index_by_name()[self].alpha2
+
+ @property
+ def alpha3(self) -> str:
+ """The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format."""
+ return _index_by_name()[self].alpha3
+
+
class ISO639_3(str):
"""ISO639_3 parses Language in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3_alpha-3)
format.
diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py
index f507779..f3a304f 100644
--- a/pydantic_extra_types/pendulum_dt.py
+++ b/pydantic_extra_types/pendulum_dt.py
@@ -4,7 +4,9 @@ CoreSchema implementation. This allows Pydantic to validate the DateTime object.
"""
try:
+ from pendulum import Date as _Date
from pendulum import DateTime as _DateTime
+ from pendulum import Duration as _Duration
from pendulum import parse
except ModuleNotFoundError: # pragma: no cover
raise RuntimeError(
@@ -72,3 +74,119 @@ class DateTime(_DateTime):
except Exception as exc:
raise PydanticCustomError('value_error', 'value is not a valid timestamp') from exc
return handler(data)
+
+
+class Date(_Date):
+ """
+ A `pendulum.Date` object. At runtime, this type decomposes into pendulum.Date automatically.
+ This type exists because Pydantic throws a fit on unknown types.
+
+ ```python
+ from pydantic import BaseModel
+ from pydantic_extra_types.pendulum_dt import Date
+
+ class test_model(BaseModel):
+ dt: Date
+
+ print(test_model(dt='2021-01-01'))
+
+ #> test_model(dt=Date(2021, 1, 1))
+ ```
+ """
+
+ __slots__: List[str] = []
+
+ @classmethod
+ def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
+ """
+ Return a Pydantic CoreSchema with the Date validation
+
+ Args:
+ source: The source type to be converted.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ A Pydantic CoreSchema with the Date validation.
+ """
+ return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.date_schema())
+
+ @classmethod
+ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any:
+ """
+ Validate the date object and return it.
+
+ Args:
+ value: The value to validate.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ The validated value or raises a PydanticCustomError.
+ """
+ # if we are passed an existing instance, pass it straight through.
+ if isinstance(value, _Date):
+ return handler(value)
+
+ # otherwise, parse it.
+ try:
+ data = parse(value)
+ except Exception as exc:
+ raise PydanticCustomError('value_error', 'value is not a valid date') from exc
+ return handler(data)
+
+
+class Duration(_Duration):
+ """
+ A `pendulum.Duration` object. At runtime, this type decomposes into pendulum.Duration automatically.
+ This type exists because Pydantic throws a fit on unknown types.
+
+ ```python
+ from pydantic import BaseModel
+ from pydantic_extra_types.pendulum_dt import Duration
+
+ class test_model(BaseModel):
+ delta_t: Duration
+
+ print(test_model(delta_t='P1DT25H'))
+
+ #> test_model(delta_t=Duration(days=2, hours=1))
+ ```
+ """
+
+ __slots__: List[str] = []
+
+ @classmethod
+ def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
+ """
+ Return a Pydantic CoreSchema with the Duration validation
+
+ Args:
+ source: The source type to be converted.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ A Pydantic CoreSchema with the Duration validation.
+ """
+ return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.timedelta_schema())
+
+ @classmethod
+ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any:
+ """
+ Validate the Duration object and return it.
+
+ Args:
+ value: The value to validate.
+ handler: The handler to get the CoreSchema.
+
+ Returns:
+ The validated value or raises a PydanticCustomError.
+ """
+ # if we are passed an existing instance, pass it straight through.
+ if isinstance(value, _Duration):
+ return handler(value)
+
+ # otherwise, parse it.
+ try:
+ data = parse(value)
+ except Exception as exc:
+ raise PydanticCustomError('value_error', 'value is not a valid duration') from exc
+ return handler(data)
diff --git a/pydantic_extra_types/phone_numbers.py b/pydantic_extra_types/phone_numbers.py
index 7acaa89..cf03417 100644
--- a/pydantic_extra_types/phone_numbers.py
+++ b/pydantic_extra_types/phone_numbers.py
@@ -4,6 +4,7 @@ The `pydantic_extra_types.phone_numbers` module provides the
This class depends on the [phonenumbers] package, which is a Python port of Google's [libphonenumber].
"""
+
from __future__ import annotations
from typing import Any, Callable, ClassVar, Generator
diff --git a/pydantic_extra_types/routing_number.py b/pydantic_extra_types/routing_number.py
index 22ea6e8..b4d53a8 100644
--- a/pydantic_extra_types/routing_number.py
+++ b/pydantic_extra_types/routing_number.py
@@ -2,6 +2,7 @@
The `pydantic_extra_types.routing_number` module provides the
[`ABARoutingNumber`][pydantic_extra_types.routing_number.ABARoutingNumber] data type.
"""
+
from typing import Any, ClassVar, Type
from pydantic import GetCoreSchemaHandler
diff --git a/pydantic_extra_types/ulid.py b/pydantic_extra_types/ulid.py
index d2bf650..5891f9f 100644
--- a/pydantic_extra_types/ulid.py
+++ b/pydantic_extra_types/ulid.py
@@ -3,6 +3,7 @@ The `pydantic_extra_types.ULID` module provides the [`ULID`] data type.
This class depends on the [python-ulid] package, which is a validate by the [ULID-spec](https://github.com/ulid/spec#implementations-in-other-languages).
"""
+
from __future__ import annotations
from dataclasses import dataclass
diff --git a/requirements/linting.txt b/requirements/linting.txt
index 9bc7bc1..07ccbb2 100644
--- a/requirements/linting.txt
+++ b/requirements/linting.txt
@@ -14,7 +14,7 @@ filelock==3.13.1
# via virtualenv
identify==2.5.35
# via pre-commit
-mypy==1.8.0
+mypy==1.9.0
# via -r requirements/linting.in
mypy-extensions==1.0.0
# via mypy
@@ -22,11 +22,11 @@ nodeenv==1.8.0
# via pre-commit
platformdirs==4.2.0
# via virtualenv
-pre-commit==3.6.2
+pre-commit==3.7.0
# via -r requirements/linting.in
pyyaml==6.0.1
# via pre-commit
-ruff==0.2.2
+ruff==0.3.5
# via -r requirements/linting.in
typing-extensions==4.10.0
# via mypy
diff --git a/requirements/testing.txt b/requirements/testing.txt
index 0c36fc5..37e7c23 100644
--- a/requirements/testing.txt
+++ b/requirements/testing.txt
@@ -10,7 +10,7 @@ charset-normalizer==3.3.2
# via requests
codecov==2.1.13
# via -r requirements/testing.in
-coverage[toml]==7.4.3
+coverage[toml]==7.4.4
# via
# -r requirements/testing.in
# codecov
@@ -31,12 +31,12 @@ pluggy==1.4.0
# via pytest
pygments==2.17.2
# via rich
-pytest==8.0.2
+pytest==8.1.1
# via
# -r requirements/testing.in
# pytest-cov
# pytest-pretty
-pytest-cov==4.1.0
+pytest-cov==5.0.0
# via -r requirements/testing.in
pytest-pretty==1.2.0
# via -r requirements/testing.in
diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py
index 5daa246..d0798bc 100644
--- a/tests/test_json_schema.py
+++ b/tests/test_json_schema.py
@@ -13,7 +13,7 @@ from pydantic_extra_types.country import (
)
from pydantic_extra_types.currency_code import ISO4217, Currency
from pydantic_extra_types.isbn import ISBN
-from pydantic_extra_types.language_code import ISO639_3, ISO639_5
+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
@@ -220,6 +220,24 @@ everyday_currencies.sort()
},
),
(
+ LanguageAlpha2,
+ {
+ 'properties': {'x': {'pattern': '^\\w{2}$', 'title': 'X', 'type': 'string'}},
+ 'required': ['x'],
+ 'title': 'Model',
+ 'type': 'object',
+ },
+ ),
+ (
+ LanguageName,
+ {
+ 'properties': {'x': {'title': 'X', 'type': 'string'}},
+ 'required': ['x'],
+ 'title': 'Model',
+ 'type': 'object',
+ },
+ ),
+ (
ISO639_3,
{
'properties': {
diff --git a/tests/test_language_codes.py b/tests/test_language_codes.py
index 27cc44a..d32cda9 100644
--- a/tests/test_language_codes.py
+++ b/tests/test_language_codes.py
@@ -1,10 +1,37 @@
import re
+from string import printable
import pycountry
import pytest
from pydantic import BaseModel, ValidationError
from pydantic_extra_types import language_code
+from pydantic_extra_types.language_code import (
+ LanguageAlpha2,
+ LanguageInfo,
+ LanguageName,
+ _index_by_alpha2,
+ _index_by_alpha3,
+ _index_by_name,
+)
+
+PARAMS_AMOUNT = 20
+
+
+@pytest.fixture(scope='module', name='MovieAlpha2')
+def movie_alpha2_fixture():
+ class Movie(BaseModel):
+ audio_lang: LanguageAlpha2
+
+ return Movie
+
+
+@pytest.fixture(scope='module', name='MovieName')
+def movie_name_fixture():
+ class Movie(BaseModel):
+ audio_lang: LanguageName
+
+ return Movie
class ISO3CheckingModel(BaseModel):
@@ -15,6 +42,34 @@ class ISO5CheckingModel(BaseModel):
lang: language_code.ISO639_5
+@pytest.mark.parametrize('alpha2, language_data', list(_index_by_alpha2().items()))
+def test_valid_alpha2(alpha2: str, language_data: LanguageInfo, MovieAlpha2):
+ the_godfather = MovieAlpha2(audio_lang=alpha2)
+ assert the_godfather.audio_lang == language_data.alpha2
+ assert the_godfather.audio_lang.alpha3 == language_data.alpha3
+ assert the_godfather.audio_lang.name == language_data.name
+
+
+@pytest.mark.parametrize('alpha2', list(printable) + list(_index_by_alpha3().keys())[:PARAMS_AMOUNT])
+def test_invalid_alpha2(alpha2: str, MovieAlpha2):
+ with pytest.raises(ValidationError, match='Invalid language alpha2 code'):
+ MovieAlpha2(audio_lang=alpha2)
+
+
+@pytest.mark.parametrize('name, language_data', list(_index_by_name().items())[:PARAMS_AMOUNT])
+def test_valid_name(name: str, language_data: LanguageInfo, MovieName):
+ the_godfather = MovieName(audio_lang=name)
+ assert the_godfather.audio_lang == language_data.name
+ assert the_godfather.audio_lang.alpha2 == language_data.alpha2
+ assert the_godfather.audio_lang.alpha3 == language_data.alpha3
+
+
+@pytest.mark.parametrize('name', set(printable) - {'E', 'U'}) # E and U are valid language codes
+def test_invalid_name(name: str, MovieName):
+ with pytest.raises(ValidationError, match='Invalid language name'):
+ MovieName(audio_lang=name)
+
+
@pytest.mark.parametrize('lang', map(lambda lang: lang.alpha_3, pycountry.languages))
def test_iso_ISO639_3_code_ok(lang: str):
model = ISO3CheckingModel(lang=lang)
diff --git a/tests/test_pendulum_dt.py b/tests/test_pendulum_dt.py
index 31306d7..18ec5ef 100644
--- a/tests/test_pendulum_dt.py
+++ b/tests/test_pendulum_dt.py
@@ -2,38 +2,119 @@ import pendulum
import pytest
from pydantic import BaseModel, ValidationError
-from pydantic_extra_types.pendulum_dt import DateTime
+from pydantic_extra_types.pendulum_dt import Date, DateTime, Duration
-class Model(BaseModel):
+class DtModel(BaseModel):
dt: DateTime
+class DateModel(BaseModel):
+ d: Date
+
+
+class DurationModel(BaseModel):
+ delta_t: Duration
+
+
def test_pendulum_dt_existing_instance():
"""
Verifies that constructing a model with an existing pendulum dt doesn't throw.
"""
now = pendulum.now()
- model = Model(dt=now)
+ model = DtModel(dt=now)
assert model.dt == now
+def test_pendulum_date_existing_instance():
+ """
+ Verifies that constructing a model with an existing pendulum date doesn't throw.
+ """
+ today = pendulum.today().date()
+ model = DateModel(d=today)
+ assert model.d == today
+
+
+def test_pendulum_duration_existing_instance():
+ """
+ Verifies that constructing a model with an existing pendulum duration doesn't throw.
+ """
+ delta_t = pendulum.duration(days=42, hours=13, minutes=37)
+ model = DurationModel(delta_t=delta_t)
+
+ assert model.delta_t.total_seconds() == delta_t.total_seconds()
+
+
@pytest.mark.parametrize(
- 'dt', [pendulum.now().to_iso8601_string(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()]
+ 'dt',
+ [
+ pendulum.now().to_iso8601_string(),
+ pendulum.now().to_w3c_string(),
+ pendulum.now().to_iso8601_string(),
+ ],
)
def test_pendulum_dt_from_serialized(dt):
"""
Verifies that building an instance from serialized, well-formed strings decode properly.
"""
dt_actual = pendulum.parse(dt)
- model = Model(dt=dt)
+ model = DtModel(dt=dt)
assert model.dt == dt_actual
+def test_pendulum_date_from_serialized():
+ """
+ Verifies that building an instance from serialized, well-formed strings decode properly.
+ """
+ date_actual = pendulum.parse('2024-03-18').date()
+ model = DateModel(d='2024-03-18')
+ assert model.d == date_actual
+
+
+@pytest.mark.parametrize(
+ 'delta_t_str',
+ [
+ 'P3.14D',
+ 'PT404H',
+ 'P1DT25H',
+ 'P2W',
+ 'P10Y10M10D',
+ ],
+)
+def test_pendulum_duration_from_serialized(delta_t_str):
+ """
+ Verifies that building an instance from serialized, well-formed strings decode properly.
+ """
+ true_delta_t = pendulum.parse(delta_t_str)
+ model = DurationModel(delta_t=delta_t_str)
+ assert model.delta_t == true_delta_t
+
+
@pytest.mark.parametrize('dt', [None, 'malformed', pendulum.now().to_iso8601_string()[:5], 42])
def test_pendulum_dt_malformed(dt):
"""
Verifies that the instance fails to validate if malformed dt are passed.
"""
with pytest.raises(ValidationError):
- Model(dt=dt)
+ DtModel(dt=dt)
+
+
+@pytest.mark.parametrize('date', [None, 'malformed', pendulum.today().to_iso8601_string()[:5], 42])
+def test_pendulum_date_malformed(date):
+ """
+ Verifies that the instance fails to validate if malformed date are passed.
+ """
+ with pytest.raises(ValidationError):
+ DateModel(d=date)
+
+
+@pytest.mark.parametrize(
+ 'delta_t',
+ [None, 'malformed', pendulum.today().to_iso8601_string()[:5], 42, '12m'],
+)
+def test_pendulum_duration_malformed(delta_t):
+ """
+ Verifies that the instance fails to validate if malformed durations are passed.
+ """
+ with pytest.raises(ValidationError):
+ DurationModel(delta_t=delta_t)
diff --git a/tests/test_ulid.py b/tests/test_ulid.py
index b9ae527..94cec2d 100644
--- a/tests/test_ulid.py
+++ b/tests/test_ulid.py
@@ -31,8 +31,8 @@ class Something(BaseModel):
(_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPE'), '01BTGNYV6HRNK8K8VKZASZCFPE', True),
(_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPF'), '01BTGNYV6HRNK8K8VKZASZCFPF', True),
# Invalid _ULID for bytes format
- (b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8', None, False), # Invalid ULID (short length)
- (b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8\xB6\x00', None, False), # Invalid ULID (long length)
+ (b'\x01\xba\x1e\xb2\x8a\x9f\xfay\x10\xd5\xa5k\xc8', None, False), # Invalid ULID (short length)
+ (b'\x01\xba\x1e\xb2\x8a\x9f\xfay\x10\xd5\xa5k\xc8\xb6\x00', None, False), # Invalid ULID (long length)
# Valid ULID for int format
(109667145845879622871206540411193812282, '2JG4FVY7N8XS4GFVHPXGJZ8S9T', True),
(109667145845879622871206540411193812283, '2JG4FVY7N8XS4GFVHPXGJZ8S9V', True),