commit dfa8240f1facc2f364dc491b7b6cf81ede108a6f Author: upstream Date: Sun Sep 6 01:23:06 2026 +0200 Upstream pydantic/pydantic-extra-types state of the touched files before 20f7f79e31 diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..f19ea91 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,280 @@ +# CHANGELOG + +## Latest Changes + +## 2.11.2 + +* fix: accept common timezone abbreviations like EDT in TimeZoneName validator by @r266-tech in https://github.com/pydantic/pydantic-extra-types/pull/376 +* fix: correct ISO 8601 serialization for negative pendulum Duration values by @r266-tech in https://github.com/pydantic/pydantic-extra-types/pull/377 +* ⬆ Bump the python-packages group with 3 updates by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/378 +* fix(python): add missing Pendulum Interval wrapper support by @RedZapdos123 in https://github.com/pydantic/pydantic-extra-types/pull/379 +* feat(isin): add ISIN type with checksum validation by @RedZapdos123 in https://github.com/pydantic/pydantic-extra-types/pull/380 +* feat(json_schema): add JsonSchema type validated by jsonschema by @RedZapdos123 in https://github.com/pydantic/pydantic-extra-types/pull/381 +* πŸ”– Release version 2.11.2 by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/382 + +## 2.11.1 + +* πŸ› fix Cron Tests (Happy New Year πŸŽ‰ ) by @anthonycicc in https://github.com/pydantic/pydantic-extra-types/pull/361 +* ⬆ Bump actions/upload-artifact from 5 to 6 by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/362 +* ⬆ Bump actions/download-artifact from 6 to 7 by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/363 +* ⬆ Bump the python-packages group with 4 updates by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/365 +* Add UUID v7 support (and v6/v8) by @sshaheerkhann in https://github.com/pydantic/pydantic-extra-types/pull/367 +* feat: migrate DSN types from pydantic.networks by @bBlazewavE in https://github.com/pydantic/pydantic-extra-types/pull/368 +* Export `Color` from package `__init__.py` by @bBlazewavE in https://github.com/pydantic/pydantic-extra-types/pull/373 +* ⬆ Bump the python-packages group with 2 updates by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/371 +* ⬆ Bump actions/download-artifact from 7 to 8 by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/370 +* ⬆ Bump actions/upload-artifact from 6 to 7 by @dependabot[bot] in https://github.com/pydantic/pydantic-extra-types/pull/369 +* feat: add IBAN (International Bank Account Number) type by @toroleapinc in https://github.com/pydantic/pydantic-extra-types/pull/372 +* πŸ”– Release version 2.11.1 by @yezz123 in https://github.com/pydantic/pydantic-extra-types/pull/375 + +## 2.11.0 + +* πŸ“ docs: improve example formatting in PhoneNumber documentation by @hengky-kurniawan-1 in +* Fix import typo in the script code's documentation by @wesley312 in +* ⬆ Bump actions/upload-artifact from 4 to 5 by @dependabot[bot] in +* ⬆ Bump astral-sh/setup-uv from 6 to 7 by @dependabot[bot] in +* ⬆ Bump actions/download-artifact from 5 to 6 by @dependabot[bot] in +* πŸ› fix: url typo in docstring by @lch in +* ♻️ raise `ValueError` instead of `AttributeError` on wrong `S3Path` value by @Danipulok in +* ⬆ Bump actions/checkout from 5 to 6 by @dependabot[bot] in +* Refactor type hints to use built-in tuple syntax by @yezz123 in +* Update version number for release 2.11.0 by @yezz123 in + +## 2.10.6 + +* feat: update credit card brands by @LorhanSohaky in +* Fix various documentation layout issues by @Viicos in +* Added test coverage for DomainStr validation including punycode and invalid inputs by @rhealakhotia in +* ✨ Add time parsing to `pendulum` by @yezz123 in +* Fix relative path creation on Windows for fixtures by @denini08 in +* ⬆ Bump samuelcolvin/check-python-version from 4.1 to 5 by @dependabot[bot] in +* ⬆ Bump actions/checkout from 4 to 5 by @dependabot[bot] in +* ⬆ Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in +* docs: fix-isbn-typos by @gem7318 in +* ♻️ refactor `mac_address` validation logic by @Stormageddon37 in +* πŸ› set `@classmethod` in `phone_numbers` schema by @Stormageddon37 in +* πŸ“ chore: fix random typos by @Stormageddon37 in +* πŸ“ docs: enhance docstring for `PhoneNumber` class with usage examples by @hengky-kurniawan-1 in +* Added cron type by @Neko1313 in +* πŸ”– Release version 2.10.6 by @yezz123 in + +## 2.10.5 + +* ⬆ Bump astral-sh/setup-uv from 5 to 6 by @dependabot in +* fix: Install `pre-commit` hooks with uv by @karta9821 in +* bump uv lockfile by @07pepa in +* Add path types by @irtimir in +* feat: Support `Decimal` type in coordinates by @karta9821 in +* πŸ› fix: serialize Pendulum zero Duration as valid ISO 8601 'P0D' by @dreglad in +* Allow constructing an ULID from an `uuid.UUID` by @abhiaagarwal in +* πŸ”– Release version 2.10.5 by @yezz123 in + +## 2.10.4 + +* Fix serialization of mongo_object_id by @MaximilianClemens in +* Default to using system zoneinfo if available by @jelly in +* fix: disallow bool as input value for ULID by @glowskir in +* ⬆ Update phonenumbers requirement from <9,>=8 to >=8,<10 in the python-packages group by @dependabot in +* :recycle: change the Package ecosystem to `uv` by @yezz123 in +* ♻️ Fix regex pattern for Domain by @rosscdh-tpg in +* Fix color parsing when TypeError by @irtimir in +* πŸ”– Release version 2.10.4 by @yezz123 in + +## 2.10.3 + +* Add support for pymongo bson ObjectId by @Ale-Cas in +* Fix checkdigit calculation for ISBN10 by @eumiro in +* Make checksum calculation more descriptive by @eumiro in +* Refactor validate_mac_address method by @eumiro in +* fix (de-)serialization of pendulum durations by @hoesler in +* limit iso8601 serialization of duration to json by @hoesler in +* πŸ”– Release version 2.10.3 by @yezz123 in + +## 2.10.2 + +* Add back Python 3.8 support by @Viicos in +* ⬆ Bump astral-sh/setup-uv from 4 to 5 by @dependabot in +* Preserve months when using the Pendulum Duration type by @gareththackeray in +* ✨ Add type checking support and improve type hints across the codebase by @yezz123 in +* πŸ“ Add additional installation information to README by @oakhan3 in + +## 2.10.1 + +* Allow build with python-ulid 3.0.0 by @sunpoet in +* πŸ”¨ added automatic syntax-upgrade hook ~ pyupgrade by @janas-adam in +* :fire: Revert adding pyupgrade as a hook in pre-commit by @yezz123 in +* isolate url in Currency by @edasubert in +* lower case currency is valid by @edasubert in +* Update SemanticVersion by @viccie30 in +* Epoch - unix timestamp by @commonism in +* :recycle: Migrate Pydantic Extra Types to use uv by @yezz123 in +* ⬆ Bump astral-sh/setup-uv from 3 to 4 by @dependabot in +* ⬆ Bump pre-commit/action from 3.0.0 to 3.0.1 by @dependabot in +* πŸ”– Release version 2.10.1 by @yezz123 in +* Fix check python version for release by @hramezani in + +## 2.10.0 + +### Types + +* Add semantic version type by @jbkroner in +* feat: add S3Path by @lucianosrp in + +### Refactor + +* feature: Improve phone number validator by @mZbZ in +* Feature: Add phone number validator by @mZbZ in +* Domain name string type by @matter1-git in +* Adjust test_json_schema() for Pydantic 2.9 by @musicinmybrain in +* Allow python-ulid 3.0 by @musicinmybrain in + +### Dependencies + +* ⬆ Bump the python-packages group with 5 updates by @dependabot in +* ✨ deprecate `semver` in favor of `semantic_version` by @07pepa in +* πŸ”– Release version 2.10.0 by @yezz123 in + +## 2.9.0 + +### Types + +* Add Semantic version type. PR [#195](https://github.com/pydantic/pydantic-extra-types/pull/195) by [@nikstuckenbrock](https://github.com/nikstuckenbrock) +* Add timezone name validation. PR [#193](https://github.com/pydantic/pydantic-extra-types/pull/193) by [@07pepa](https://github.com/07pepa) + +### Refactor + +* Replace try-except block by if-else statement. PR [#192](https://github.com/pydantic/pydantic-extra-types/pull/192) by [@maxsos](https://github.com/maxsos) + +### Dependencies + +* ⬆ Bump the python-packages group with 4 updates. PR [#194](https://github.com/pydantic/pydantic-extra-types/pull/194) by @dependabot + +## 2.8.2 + +* πŸ› Preserve timezone information when validating Pendulum DateTimes. [#189](https://github.com/pydantic/pydantic-extra-types/pull/189) by [@chrisguidry +](https://github.com/chrisguidry) + +## 2.8.1 + +### Bug Fixes + +* πŸ› Fix Pendulum date time object to have correct typing. [#184](https://github.com/pydantic/pydantic-extra-types/pull/184) by [@07pepa](https://github.com/07pepa) + +### Types + +* ✨ Add parsing of pendulum_dt from unix time and non-strict parsing. [#185](https://github.com/pydantic/pydantic-extra-types/pull/185) by [@07pepa](https://github.com/07pepa) + +## 2.8.0 + +### Refactor + +* ♻️ refactor some functions & minor changes. [#180](https://github.com/pydantic/pydantic-extra-types/pull/180) by [@yezz123](https://github.com/yezz123) + +### Internal + +* Allow requiring extra dependencies. [#178](https://github.com/pydantic/pydantic-extra-types/pull/178) by [@yezz123](https://github.com/yezz123) + +### Types + +* Add ISO 15924 and tests. [#174](https://github.com/pydantic/pydantic-extra-types/pull/174) by [@07pepa](https://github.com/07pepa) +* add native datetime to pendulum_dt.py. [#176](https://github.com/pydantic/pydantic-extra-types/pull/176) by [@07pepa](https://github.com/07pepa) +* add hash and eq to phone_numbers. [#177](https://github.com/pydantic/pydantic-extra-types/pull/177) by [@07pepa](https://github.com/07pepa) + +### Dependencies + +* ⬆ Bump the python-packages group with 5 updates. PR [#179](https://github.com/pydantic/pydantic-extra-types/pull/179) by @dependabot +* ⬆ Bump the python-packages group with 4 updates. PR [#171](https://github.com/pydantic/pydantic-extra-types/pull/171) by @dependabot + +## 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/pydantic_extra_types/mac_address.py b/pydantic_extra_types/mac_address.py new file mode 100644 index 0000000..6838aa3 --- /dev/null +++ b/pydantic_extra_types/mac_address.py @@ -0,0 +1,109 @@ +"""The MAC address module provides functionality to parse and validate MAC addresses in different +formats, such as IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet format. +""" + +from __future__ import annotations + +from typing import Any + +from pydantic import GetCoreSchemaHandler +from pydantic_core import PydanticCustomError, core_schema + +MINIMUM_LENGTH: int = 14 +ALLOWED_CHUNK_COUNTS: tuple[int, int, int] = (6, 8, 20) + + +class MacAddress(str): + """Represents a MAC address and provides methods for conversion, validation, and serialization. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.mac_address import MacAddress + + + class Network(BaseModel): + mac_address: MacAddress + + + network = Network(mac_address='00:00:5e:00:53:01') + print(network) + # > mac_address='00:00:5e:00:53:01' + ``` + """ + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + """Return a Pydantic CoreSchema with the MAC address validation. + + Args: + source: The source type to be converted. + handler: The handler to get the CoreSchema. + + Returns: + A Pydantic CoreSchema with the MAC address validation. + + """ + return core_schema.with_info_before_validator_function( + cls._validate, + core_schema.str_schema(), + ) + + @classmethod + def _validate(cls, __input_value: Any, _: Any) -> str: + """Validate a MAC Address from the provided value. + + Args: + __input_value: The value to be validated. + _: The source type to be converted. + + Returns: + str: The parsed MAC address. + + """ + if not isinstance(__input_value, str): + raise PydanticCustomError('mac_address_type', 'Value must be a string') + + return cls.validate_mac_address(__input_value.encode()) + + @staticmethod + def validate_mac_address(value: bytes) -> str: + """Validate a MAC Address from the provided byte value.""" + raw = value.decode() + if len(raw) < MINIMUM_LENGTH: + raise PydanticCustomError( + 'mac_address_len', + 'Length for a {mac_address} MAC address must be {required_length}', + {'mac_address': raw, 'required_length': MINIMUM_LENGTH}, + ) + + for seperator, chunk_len in ((':', 2), ('-', 2), ('.', 4)): + if seperator not in raw: + continue + + parts = raw.split(seperator) + if any(len(p) != chunk_len for p in parts): + raise PydanticCustomError( + 'mac_address_format', + f'Must have the format xx{seperator}xx{seperator}xx{seperator}xx{seperator}xx{seperator}xx', + ) + + total_bytes = (len(parts) * chunk_len) // 2 + if total_bytes not in ALLOWED_CHUNK_COUNTS: + raise PydanticCustomError( + 'mac_address_format', + 'Length for a {mac_address} MAC address must be {required_length}', + {'mac_address': raw, 'required_length': ALLOWED_CHUNK_COUNTS}, + ) + + try: + mac_bytes: list[int] = [] + for part in parts: + for i in range(0, chunk_len, 2): + mac_bytes.append(int(part[i : i + 2], base=16)) + except ValueError as exc: + raise PydanticCustomError('mac_address_format', 'Unrecognized format') from exc + + return ':'.join(f'{b:02x}' for b in mac_bytes) + + raise PydanticCustomError('mac_address_format', 'Unrecognized format') diff --git a/tests/test_mac_address.py b/tests/test_mac_address.py new file mode 100644 index 0000000..82f78c9 --- /dev/null +++ b/tests/test_mac_address.py @@ -0,0 +1,150 @@ +from typing import Any + +import pytest +from pydantic import BaseModel, ValidationError + +from pydantic_extra_types.mac_address import MacAddress + + +class Network(BaseModel): + mac_address: MacAddress + + +@pytest.mark.parametrize( + 'mac_address, result, valid', + [ + # Valid MAC addresses + ('00:00:5e:00:53:01', '00:00:5e:00:53:01', True), + ('02:00:5e:10:00:00:00:01', '02:00:5e:10:00:00:00:01', True), + ( + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + ('00-00-5e-00-53-01', '00:00:5e:00:53:01', True), + ('02-00-5e-10-00-00-00-01', '02:00:5e:10:00:00:00:01', True), + ( + '00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + ('0000.5e00.5301', '00:00:5e:00:53:01', True), + ('0200.5e10.0000.0001', '02:00:5e:10:00:00:00:01', True), + ( + '0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + # Invalid MAC addresses + ('0200.5e10.0000.001', None, False), + ('00-00-5e-00-53-0', None, False), + ('00:00:5e:00:53:1', None, False), + ('02:00:5e:10:00:00:00:1', None, False), + ('00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:1', None, False), + ('0200.5e10.0000.001', None, False), # Invalid length + ('00-00-5e-00-53-0', None, False), # Missing character + ('00:00:5e:00:53:1', None, False), # Missing leading zero + ('00:00:5g:00:53:01', None, False), # Invalid hex digit 'g' + ('00.00.5e.0.3.01.0.0.5e.0.53.01', None, False), + ('00-00-5e-00-53-01:', None, False), # Extra separator at the end + ('00000.5e000.5301', None, False), + ('000.5e0.530001', None, False), + ('0000.5e#0./301', None, False), + (b'12.!4.5!.7/.#G.AB......', None, False), + ('12.!4.5!.7/.#G.AB', None, False), + ('00-00-5e-00-53-01-', None, False), # Extra separator at the end + ('00.00.5e.00.53.01.', None, False), # Extra separator at the end + ('00:00:5e:00:53:', None, False), # Incomplete MAC address + (float(12345678910111213), None, False), + ], +) +def test_format_for_mac_address(mac_address: Any, result: str, valid: bool): + if valid: + assert Network(mac_address=MacAddress(mac_address)).mac_address == result + else: + with pytest.raises(ValidationError, match='format'): + Network(mac_address=MacAddress(mac_address)) + + +@pytest.mark.parametrize( + 'mac_address, result, valid', + [ + # Valid MAC addresses + ('00:00:5e:00:53:01', '00:00:5e:00:53:01', True), + ('02:00:5e:10:00:00:00:01', '02:00:5e:10:00:00:00:01', True), + ( + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + ('00-00-5e-00-53-01', '00:00:5e:00:53:01', True), + ('02-00-5e-10-00-00-00-01', '02:00:5e:10:00:00:00:01', True), + ( + '00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + ('0000.5e00.5301', '00:00:5e:00:53:01', True), + ('0200.5e10.0000.0001', '02:00:5e:10:00:00:00:01', True), + ( + '0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001', + '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + True, + ), + # Invalid MAC addresses + ('0', None, False), + ('00:00:00', None, False), + ('00-00-5e-00-53-01-01', None, False), + ('0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001.0000.0001', None, False), + ], +) +def test_length_for_mac_address(mac_address: str, result: str, valid: bool): + if valid: + assert Network(mac_address=MacAddress(mac_address)).mac_address == result + else: + with pytest.raises(ValueError, match='Length'): + Network(mac_address=MacAddress(mac_address)) + + +@pytest.mark.parametrize( + 'mac_address, valid', + [ + # Valid MAC addresses + ('00:00:5e:00:53:01', True), + (MacAddress('00:00:5e:00:53:01'), True), + # Invalid MAC addresses + (0, False), + (['00:00:00'], False), + ], +) +def test_type_for_mac_address(mac_address: Any, valid: bool): + if valid: + Network(mac_address=MacAddress(mac_address)) + else: + with pytest.raises(ValidationError, match='MAC address must be 14'): + Network(mac_address=MacAddress(mac_address)) + + +def test_model_validation(): + class Model(BaseModel): + mac_address: MacAddress + + assert Model(mac_address='00:00:5e:00:53:01').mac_address == '00:00:5e:00:53:01' + with pytest.raises(ValidationError) as exc_info: + Model(mac_address='1234') + + assert exc_info.value.errors() == [ + { + 'ctx': {'mac_address': '1234', 'required_length': 14}, + 'input': '1234', + 'loc': ('mac_address',), + 'msg': 'Length for a 1234 MAC address must be 14', + 'type': 'mac_address_len', + } + ] + + +@pytest.mark.parametrize('mac_address_value', [None, 12345, 1.5, b'bytes', [], {}]) +def test_mac_address_requires_string(mac_address_value: Any) -> None: + with pytest.raises(ValidationError, match='Value must be a string'): + Network(mac_address=mac_address_value)