From b2aca1029eeb9d53e26ee74f6b189813ea45ed48 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 20 Sep 2023 16:28:07 +0200 Subject: [PATCH] Refactor documentation --- docs/color_types.md | 87 -------------------- docs/coordinate.md | 49 ------------ docs/extra/terminal.css | 39 --------- docs/extra/tweaks.css | 77 ------------------ docs/favicon.png | Bin 891 -> 0 bytes docs/logo-white.svg | 3 - docs/mac_address.md | 22 ----- docs/payment_cards.md | 54 ------------- docs/phone_numbers.md | 27 ------- docs/routing_numbers.md | 31 -------- docs/theme/main.html | 10 --- mkdocs.yml | 74 ----------------- pydantic_extra_types/coordinate.py | 61 +++++++++++++- pydantic_extra_types/country.py | 106 ++++++++++++++++++++++++- pydantic_extra_types/mac_address.py | 38 +++++---- pydantic_extra_types/payment.py | 31 +++----- pydantic_extra_types/phone_numbers.py | 19 ++++- pydantic_extra_types/routing_number.py | 56 ++++++++++--- pyproject.toml | 2 +- 19 files changed, 258 insertions(+), 528 deletions(-) delete mode 100644 docs/color_types.md delete mode 100644 docs/coordinate.md delete mode 100644 docs/extra/terminal.css delete mode 100644 docs/extra/tweaks.css delete mode 100644 docs/favicon.png delete mode 100644 docs/logo-white.svg delete mode 100644 docs/mac_address.md delete mode 100644 docs/payment_cards.md delete mode 100644 docs/phone_numbers.md delete mode 100644 docs/routing_numbers.md delete mode 100644 docs/theme/main.html delete mode 100644 mkdocs.yml diff --git a/docs/color_types.md b/docs/color_types.md deleted file mode 100644 index e2e03291..00000000 --- a/docs/color_types.md +++ /dev/null @@ -1,87 +0,0 @@ - -`Color` parses HTML and CSS colors. - -You can use the `Color` data type for storing colors as per -[CSS3 specification](http://www.w3.org/TR/css3-color/#svg-color). Colors can be defined via: - -- [name](http://www.w3.org/TR/SVG11/types.html#ColorKeywords) (e.g. `"Black"`, `"azure"`) -- [hexadecimal value](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) - (e.g. `"0x000"`, `"#FFFFFF"`, `"7fffd4"`) -- RGB/RGBA tuples (e.g. `(255, 255, 255)`, `(255, 255, 255, 0.5)`) -- [RGB/RGBA strings](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#RGB_colors) - (e.g. `"rgb(255, 255, 255)"`, `"rgba(255, 255, 255, 0.5)"`) -- [HSL strings](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#HSL_colors) - (e.g. `"hsl(270, 60%, 70%)"`, `"hsl(270, 60%, 70%, .5)"`) - -```py -from pydantic import BaseModel, ValidationError - -from pydantic_extra_types import Color - -c = Color('ff00ff') -print(c.as_named()) -#> magenta -print(c.as_hex()) -#> #f0f -c2 = Color('green') -print(c2.as_rgb_tuple()) -#> (0, 128, 0) -print(c2.original()) -#> green -print(repr(Color('hsl(180, 100%, 50%)'))) -#> Color('cyan', rgb=(0, 255, 255)) - - -class Model(BaseModel): - color: Color - - -print(Model(color='purple')) -#> color=Color('purple', rgb=(128, 0, 128)) -try: - Model(color='hello') -except ValidationError as e: - print(e) - """ - 1 validation error for Model - color - value is not a valid color: string not recognised as a valid color [type=color_error, input_value='hello', input_type=str] - """ -``` - -`Color` has the following methods: - -**`original`** -: the original string or tuple passed to `Color` - -**`as_named`** -: returns a named CSS3 color; fails if the alpha channel is set or no such color exists unless - `fallback=True` is supplied, in which case it falls back to `as_hex` - -**`as_hex`** -: returns a string in the format `#fff` or `#ffffff`; will contain 4 (or 8) hex values if the alpha channel is set, - e.g. `#7f33cc26` - -**`as_rgb`** -: returns a string in the format `rgb(, , )`, or `rgba(, , , )` - if the alpha channel is set - -**`as_rgb_tuple`** -: returns a 3- or 4-tuple in RGB(a) format. The `alpha` keyword argument can be used to define whether - the alpha channel should be included; - options: `True` - always include, `False` - never include, `None` (default) - include if set - -**`as_hsl`** -: string in the format `hsl(, , )` - or `hsl(, , , )` if the alpha channel is set - -**`as_hsl_tuple`** -: returns a 3- or 4-tuple in HSL(a) format. The `alpha` keyword argument can be used to define whether - the alpha channel should be included; - options: `True` - always include, `False` - never include, `None` (the default) - include if set - -The `__str__` method for `Color` returns `self.as_named(fallback=True)`. - -!!! note - The `as_hsl*` refer to hue, saturation, lightness "HSL" as used in html and most of the world, **not** - "HLS" as used in Python's `colorsys`. diff --git a/docs/coordinate.md b/docs/coordinate.md deleted file mode 100644 index 34a51ac1..00000000 --- a/docs/coordinate.md +++ /dev/null @@ -1,49 +0,0 @@ - -Coordinate parses Latitude and Longitude. - -You can use the `Coordinate` data type for storing coordinates. Coordinates can be defined using one of the following formats: - -1. Tuple format: `(Latitude, Longitude)`. For example: `(41.40338, 2.17403)`. -2. `Coordinate` instance format: `Coordinate(latitude=Latitude, longitude=Longitude)`. For example: `Coordinate(latitude=41.40338, longitude=2.17403)`. - -The `Latitude` class and `Longitude` class, which are used to represent latitude and longitude, respectively, enforce the following valid ranges for their values: - -- `Latitude`: The latitude value should be between -90 and 90, inclusive. -- `Longitude`: The longitude value should be between -180 and 180, inclusive. - -```py -from pydantic import BaseModel - -from pydantic_extra_types.coordinate import Longitude, Latitude, Coordinate - - -class Lat(BaseModel): - lat: Latitude - - -class Lng(BaseModel): - lng: Longitude - - -class Coord(BaseModel): - coord: Coordinate - - -lat = Lat( - lat='90.0', -) - -lng = Lng( - long='180.0' -) - -coord = Coord( - coord=('90.0', '180.0') -) -print(lat.lat) -# > 90.0 -print(lng.lng) -# > 180.0 -print(coord.coord) -# > 90.0,180.0 -``` diff --git a/docs/extra/terminal.css b/docs/extra/terminal.css deleted file mode 100644 index 13224e75..00000000 --- a/docs/extra/terminal.css +++ /dev/null @@ -1,39 +0,0 @@ -.terminal { - background: #300a24; - border-radius: 4px; - padding: 5px 10px; -} - -pre.terminal-content { - display: inline-block; - line-height: 1.3 !important; - white-space: pre-wrap; - word-wrap: break-word; - background: #300a24 !important; - color: #d0d0d0 !important; -} - -.ansi2 { - font-weight: lighter; -} -.ansi3 { - font-style: italic; -} -.ansi32 { - color: #00aa00; -} -.ansi34 { - color: #5656fe; -} -.ansi35 { - color: #E850A8; -} -.ansi38-1 { - color: #cf0000; -} -.ansi38-5 { - color: #E850A8; -} -.ansi38-68 { - color: #2a54a8; -} diff --git a/docs/extra/tweaks.css b/docs/extra/tweaks.css deleted file mode 100644 index ad33f6b9..00000000 --- a/docs/extra/tweaks.css +++ /dev/null @@ -1,77 +0,0 @@ -.sponsors { - display: flex; - justify-content: center; - flex-wrap: wrap; - align-items: center; - margin: 1rem 0; -} - -.sponsors > div { - text-align: center; - width: 33%; - padding-bottom: 20px; -} - -.sponsors span { - display: block; -} - -@media screen and (max-width: 599px) { - .sponsors span { - display: none; - } -} - -.sponsors img { - width: 65%; - border-radius: 5px; -} - -/*blog post*/ -aside.blog { - display: flex; - align-items: center; -} - -aside.blog img { - width: 50px; - height: 50px; - border-radius: 25px; - margin-right: 20px; -} - -/* Define the company grid layout */ - -#grid-container { - width: 100%; - text-align: center; -} - -#company-grid { - display: inline-block; - margin: 0 auto; - gap: 10px; - align-content: center; - justify-content: center; - grid-auto-flow: column; -} - -[data-md-color-scheme="slate"] #company-grid { - background-color: #ffffff; - border-radius: .5rem; -} - -.tile { - display: flex; - text-align: center; - width: 120px; - height: 120px; - display: inline-block; - margin: 10px; - padding: 5px; - border-radius: .5rem; -} - -.tile img { - width: 100px; -} diff --git a/docs/favicon.png b/docs/favicon.png deleted file mode 100644 index c5ea1d13da1a3ff497d42727a93a097f1a7b4e8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 891 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SUOx&gwB#~18e3lwB8@$_|Nf6T=$sLC~Op|BPM1LHGK7sn8f<8!atXH0gKIR4T9 z9uHqx#$2r}8m>APA|W!abAn`KTAFrA#gv>`<<0%**$O|tveFIS-8*)gc6%FWIC-Hn+{qQj(?qtybi=C%bng=U&>=wM#&iSJdGOenakf3Svo z{x-cEh3N-=bT0qLan09UruEsrYfsq@Y?iy7EyODEapl}ulU1!gKA0?+mQ$MYy`nSE z=7!PXInC>sU+G`$W@mqi_IRMM?OBA z`HnNNGkn>tc=Np-j~sOaOC@5DR4)3LD?e6k-@;;MySZ4a=J@BuRW55*{3~1i_j_Z#y6Dwo zrEm4t%)guAxBI@TfbQ$ww@=oT?LEuuKj-M-ZQIvri0A9@&VG@Z+b(wZ+d9thGqYc? z`Q@!U*w`~`QmXvl7rERdP`( zkYX@0FtpS)G|)9L4>2^iGB&p|G|)CMvNAB(nA-zNln4#E`6-!cmAEw=Ffcg`)F276 zAviy+q&%@Gm7%=6TrV>(yEr+qAXP8FD1G)j8!4co@T!oAlAy$Lg@U5|w9K4Tg_6pG zRE5lfl4J&kiaC!z@o*G|X=t4CKYhmYX%GXmGPhnbx3IFX_hb=fVFi~4lfx;@%9}$J qPT#n4;>ejJGDp}?H+U@Y(qnifE?Dx($#g2v3I - - diff --git a/docs/mac_address.md b/docs/mac_address.md deleted file mode 100644 index 1c818e42..00000000 --- a/docs/mac_address.md +++ /dev/null @@ -1,22 +0,0 @@ - -The `MacAddress` type validates [MAC address](https://en.wikipedia.org/wiki/MAC_address) (such as a network card). - -```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 -``` - -The algorithm used to validate the MAC address `IEEE` `802` `MAC-48`, `EUI-48`, `EUI-64`, or a `20-octet`. diff --git a/docs/payment_cards.md b/docs/payment_cards.md deleted file mode 100644 index 8d0f7c07..00000000 --- a/docs/payment_cards.md +++ /dev/null @@ -1,54 +0,0 @@ - -The `PaymentCardNumber` type validates [payment cards] -(such as a debit or credit card). - -```py -from datetime import date - -from pydantic import BaseModel, constr - -from pydantic_extra_types.payment import PaymentCardBrand, PaymentCardNumber - - -class Card(BaseModel): - name: constr(strip_whitespace=True, min_length=1) - number: PaymentCardNumber - exp: date - - @property - def brand(self) -> PaymentCardBrand: - return self.number.brand - - @property - def expired(self) -> bool: - return self.exp < date.today() - - -card = Card( - name='Georg Wilhelm Friedrich Hegel', - number='4000000000000002', - exp=date(2023, 9, 30), -) - -assert card.number.brand == PaymentCardBrand.visa -assert card.number.bin == '400000' -assert card.number.last4 == '0002' -assert card.number.masked == '400000******0002' -``` - -`PaymentCardBrand` can be one of the following based on the BIN: - -* `PaymentCardBrand.amex` -* `PaymentCardBrand.mastercard` -* `PaymentCardBrand.visa` -* `PaymentCardBrand.other` - -The actual validation verifies the card number is: - -* a `str` of only digits -* [luhn](https://en.wikipedia.org/wiki/Luhn_algorithm) valid -* the correct length based on the BIN, if Amex, Mastercard or Visa, and between - 12 and 19 digits for all other brands - - -[payment cards]: https://en.wikipedia.org/wiki/Payment_card diff --git a/docs/phone_numbers.md b/docs/phone_numbers.md deleted file mode 100644 index ea1f0e71..00000000 --- a/docs/phone_numbers.md +++ /dev/null @@ -1,27 +0,0 @@ - -The `PhoneNumber` type validates phone numbers. - -This class depends on the [phonenumbers] package, which is a Python port of Google's [libphonenumber]. - -```py -from pydantic import BaseModel - -from pydantic_extra_types.phone_numbers import PhoneNumber - - -class User(BaseModel): - name: str - phone_number: PhoneNumber - - -user = User(name='John', phone_number='+447911123456') -print(user.phone_number) # (1)! -#> tel:+44-7911-123456 -``` - -1. The phone format used is described on the [RFC3966]. - - -[phonenumbers]: https://pypi.org/project/phonenumbers/ -[libphonenumber]: https://github.com/google/libphonenumber/ -[RFC3966]: https://tools.ietf.org/html/rfc3966 diff --git a/docs/routing_numbers.md b/docs/routing_numbers.md deleted file mode 100644 index 7b2f434f..00000000 --- a/docs/routing_numbers.md +++ /dev/null @@ -1,31 +0,0 @@ - -The `ABARoutingNumber` type validates [ABA routing transit numbers]. - - -```py -from pydantic import BaseModel - -from pydantic_extra_types.routing_number import ABARoutingNumber - - -class BankAccount(BaseModel): - name: str - routing_number: ABARoutingNumber - account_number: str - - -account = BankAccount( - name="John", - routing_number="122105155", - account_number="123456789", -) - -print(account.routing_number) -# > 122105155 -``` - -The algorithm used to validate the routing number is described on this [section of the Wikipedia page]. - - -[ABA routing transit numbers]: https://en.wikipedia.org/wiki/ABA_routing_transit_number -[section of the Wikipedia page]: https://en.wikipedia.org/wiki/ABA_routing_transit_number#Check_digit diff --git a/docs/theme/main.html b/docs/theme/main.html deleted file mode 100644 index c99ba9d8..00000000 --- a/docs/theme/main.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base.html" %} - -{% block announce %} - {% include 'announce.html' ignore missing %} -{% endblock %} - -{% block content %} - {{ super() }} - -{% endblock %} diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 3d2b69b3..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,74 +0,0 @@ -site_name: pydantic -site_description: Data validation using Python type hints -strict: true -site_url: https://docs.pydantic.dev/ - -theme: - name: 'material' - custom_dir: 'docs/theme' - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: pink - accent: pink - toggle: - icon: material/lightbulb-outline - name: "Switch to dark mode" - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: pink - accent: pink - toggle: - icon: material/lightbulb - name: "Switch to light mode" - features: - - content.tabs.link - - content.code.annotate - - announce.dismiss - - navigation.tabs - logo: 'logo-white.svg' - favicon: 'favicon.png' - -repo_name: pydantic/pydantic -repo_url: https://github.com/pydantic/pydantic -edit_uri: edit/main/docs/ - -extra_css: -- 'extra/terminal.css' -- 'extra/tweaks.css' - -nav: -- Home: - - Color: 'color_types.md' - - Payment Card: 'payment_cards.md' - - Phone Number: 'phone_numbers.md' - - ABA Routing Number: 'routing_number.md' - - MAC address: 'mac_address.md' - - Coordinate: 'coordinate.md' - -markdown_extensions: -- tables -- toc: - permalink: true - title: Page contents -- admonition -- pymdownx.highlight -- pymdownx.extra -- pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg -- pymdownx.tabbed: - alternate_style: true - -extra: - version: - provider: mike - -plugins: -- mike: - alias_type: symlink - canonical_version: latest -- search -- exclude: - glob: - - __pycache__/* diff --git a/pydantic_extra_types/coordinate.py b/pydantic_extra_types/coordinate.py index c0506529..0fd4405c 100644 --- a/pydantic_extra_types/coordinate.py +++ b/pydantic_extra_types/coordinate.py @@ -1,14 +1,32 @@ +""" +The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_extra_types.coordinate.Latitude], +[`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, Union +from typing import Any, ClassVar, Tuple, Type from pydantic import GetCoreSchemaHandler from pydantic._internal import _repr from pydantic_core import ArgsKwargs, PydanticCustomError, core_schema -CoordinateValueType = Union[str, int, float] - class Latitude(float): + """Latitude value should be between -90 and 90, inclusive. + + ```py + from pydantic import BaseModel + from pydantic_extra_types.coordinate import Latitude + + class Location(BaseModel): + latitude: Latitude + + location = Location(latitude=41.40338) + print(location) + #> latitude=41.40338 + ``` + """ + min: ClassVar[float] = -90.00 max: ClassVar[float] = 90.00 @@ -18,6 +36,22 @@ def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaH class Longitude(float): + """Longitude value should be between -180 and 180, inclusive. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.coordinate import Longitude + + class Location(BaseModel): + longitude: Longitude + + location = Location(longitude=2.17403) + print(location) + #> longitude=2.17403 + ``` + """ + min: ClassVar[float] = -180.00 max: ClassVar[float] = 180.00 @@ -28,6 +62,27 @@ def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaH @dataclass class Coordinate(_repr.Representation): + """Coordinate parses Latitude and Longitude. + + You can use the `Coordinate` data type for storing coordinates. Coordinates can be + defined using one of the following formats: + + 1. Tuple: `(Latitude, Longitude)`. For example: `(41.40338, 2.17403)`. + 2. `Coordinate` instance: `Coordinate(latitude=Latitude, longitude=Longitude)`. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.coordinate import Coordinate + + class Location(BaseModel): + coordinate: Coordinate + + location = Location(coordinate=(41.40338, 2.17403)) + #> coordinate=Coordinate(latitude=41.40338, longitude=2.17403) + ``` + """ + _NULL_ISLAND: ClassVar[Tuple[float, float]] = (0.0, 0.0) latitude: Latitude diff --git a/pydantic_extra_types/country.py b/pydantic_extra_types/country.py index 381bbff7..c930db1f 100644 --- a/pydantic_extra_types/country.py +++ b/pydantic_extra_types/country.py @@ -1,6 +1,5 @@ """ -Country definitions that are based on the ISO 3166 format -Based on: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes +Country definitions that are based on the [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). """ from __future__ import annotations @@ -69,6 +68,23 @@ def _index_by_official_name() -> dict[str, CountryInfo]: 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. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.country import CountryAlpha2 + + class Product(BaseModel): + made_in: CountryAlpha2 + + product = Product(made_in='ES') + print(product) + #> made_in='ES' + ``` + """ + @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryAlpha2: if __input_value not in _index_by_alpha2(): @@ -94,23 +110,44 @@ def __get_pydantic_json_schema__( @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_alpha2()[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_alpha2()[self].numeric_code @property 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) + format. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.country import CountryAlpha3 + + class Product(BaseModel): + made_in: CountryAlpha3 + + product = Product(made_in="USA") + print(product) + #> made_in='USA' + ``` + """ + @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryAlpha3: if __input_value not in _index_by_alpha3(): @@ -137,23 +174,44 @@ def __get_pydantic_json_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_alpha3()[self].alpha2 @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_alpha3()[self].numeric_code @property 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 + [ISO 3166-1 numeric](https://en.wikipedia.org/wiki/ISO_3166-1_numeric) format. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.country import CountryNumericCode + + class Product(BaseModel): + made_in: CountryNumericCode + + product = Product(made_in="840") + print(product) + #> made_in='840' + ``` + """ + @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryNumericCode: if __input_value not in _index_by_numeric_code(): @@ -180,23 +238,43 @@ def __get_pydantic_json_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_numeric_code()[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_numeric_code()[self].alpha3 @property 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. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.country import CountryShortName + + class Product(BaseModel): + made_in: CountryShortName + + product = Product(made_in="United States") + print(product) + #> made_in='United States' + ``` + """ + @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryShortName: if __input_value not in _index_by_short_name(): @@ -215,23 +293,43 @@ def __get_pydantic_core_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_short_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_short_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_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(): @@ -250,16 +348,20 @@ def __get_pydantic_core_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/pydantic_extra_types/mac_address.py b/pydantic_extra_types/mac_address.py index 6c3f0437..15648e0e 100644 --- a/pydantic_extra_types/mac_address.py +++ b/pydantic_extra_types/mac_address.py @@ -1,25 +1,33 @@ """ -MAC address Parsing and Validation - -This code 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. It includes a `MacAddress` class that represents a Mac Address and provides -methods for conversion, validation, and serialization. The code also includes a `validate_mac_address` function -that takes a byte value representing a Mac Address and returns the parsed Mac Address as a string. +The `pydantic_extra_types.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, Union +from typing import Any from pydantic import GetCoreSchemaHandler from pydantic_core import PydanticCustomError, core_schema -MacAddressType = Union[str, bytes] - class MacAddress(str): - """ - Represents a mac address - IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet + """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 @@ -36,16 +44,16 @@ def _validate(cls, __input_value: str, _: Any) -> str: @staticmethod def validate_mac_address(value: bytes) -> str: """ - Validate a Mac Address from the provided byte value. + Validate a MAC Address from the provided byte value. Args: - value (bytes): The byte value representing the Mac Address. + value: The byte value representing the MAC address. Returns: - str: The parsed Mac Address as a string. + str: The parsed MAC address. Raises: - PydanticCustomError: If the value is not a valid Mac Address. + PydanticCustomError: If the value is not a valid MAC address. """ if len(value) < 14: raise PydanticCustomError( diff --git a/pydantic_extra_types/payment.py b/pydantic_extra_types/payment.py index 386f70ac..a3bfa73a 100644 --- a/pydantic_extra_types/payment.py +++ b/pydantic_extra_types/payment.py @@ -1,6 +1,6 @@ """ -Represents and validates payment cards (such as a debit or credit card), -including validation for payment card number and issuer. +The `pydantic_extra_types.payment` module provides the +[`PaymentCardNumber`][pydantic_extra_types.payment.PaymentCardNumber] data type. """ from __future__ import annotations @@ -13,15 +13,7 @@ class PaymentCardBrand(str, Enum): - """Enumeration of payment card brands. - - `PaymentCardBrand` can be one of the following based on the BIN: - - * PaymentCardBrand.amex - * PaymentCardBrand.mastercard - * PaymentCardBrand.visa - * PaymentCardBrand.other - """ + """Payment card brands supported by the [`PaymentCardNumber`][pydantic_extra_types.payment.PaymentCardNumber].""" amex = 'American Express' mastercard = 'Mastercard' @@ -34,23 +26,20 @@ def __str__(self) -> str: class PaymentCardNumber(str): - """A [payment card number](https://en.wikipedia.org/wiki/Payment_card_number). - - Attributes: - strip_whitespace: Whether to strip whitespace from the input value. - min_length: The minimum length of the card number. - max_length: The maximum length of the card number. - bin: The first 6 digits of the card number. - last4: The last 4 digits of the card number. - brand: The brand of the card. - """ + """A [payment card number](https://en.wikipedia.org/wiki/Payment_card_number).""" strip_whitespace: ClassVar[bool] = True + """Whether to strip whitespace from the input value.""" min_length: ClassVar[int] = 12 + """The minimum length of the card number.""" max_length: ClassVar[int] = 19 + """The maximum length of the card number.""" bin: str + """The first 6 digits of the card number.""" last4: str + """The last 4 digits of the card number.""" brand: PaymentCardBrand + """The brand of the card.""" def __init__(self, card_number: str): self.validate_digits(card_number) diff --git a/pydantic_extra_types/phone_numbers.py b/pydantic_extra_types/phone_numbers.py index 62ac3205..4c25e452 100644 --- a/pydantic_extra_types/phone_numbers.py +++ b/pydantic_extra_types/phone_numbers.py @@ -1,6 +1,12 @@ +""" +The `pydantic_extra_types.phone_numbers` module provides the +[`PhoneNumber`][pydantic_extra_types.phone_numbers.PhoneNumber] data type. + +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, Generator +from typing import Any, Callable, ClassVar, Generator from pydantic import GetCoreSchemaHandler from pydantic_core import PydanticCustomError, core_schema @@ -17,16 +23,23 @@ class PhoneNumber(str): """ - An international phone number + A wrapper around [phonenumbers](https://pypi.org/project/phonenumbers/) package, which + is a Python port of Google's [libphonenumber](https://github.com/google/libphonenumber/). """ supported_regions: list[str] = sorted(phonenumbers.SUPPORTED_REGIONS) + """The supported regions.""" supported_formats: list[str] = sorted([f for f in phonenumbers.PhoneNumberFormat.__dict__.keys() if f.isupper()]) + """The supported phone number formats.""" - default_region_code: str | None = None + default_region_code: ClassVar[str | None] = None + """The default region code to use when parsing phone numbers without an international prefix.""" phone_format: str = 'RFC3966' + """The format of the phone number.""" min_length: int = 7 + """The minimum length of the phone number.""" max_length: int = 64 + """The maximum length of the phone number.""" @classmethod def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: diff --git a/pydantic_extra_types/routing_number.py b/pydantic_extra_types/routing_number.py index 1355cf22..e2b083a9 100644 --- a/pydantic_extra_types/routing_number.py +++ b/pydantic_extra_types/routing_number.py @@ -1,3 +1,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 @@ -5,20 +9,40 @@ class ABARoutingNumber(str): + """The `ABARoutingNumber` data type is a string of 9 digits representing an ABA routing transit number. + + The algorithm used to validate the routing number is described in the + [ABA routing transit number](https://en.wikipedia.org/wiki/ABA_routing_transit_number#Check_digit) + Wikipedia article. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.routing_number import ABARoutingNumber + + class BankAccount(BaseModel): + routing_number: ABARoutingNumber + + account = BankAccount(routing_number='122105155') + print(account) + #> routing_number='122105155' + ``` + """ + strip_whitespace: ClassVar[bool] = True min_length: ClassVar[int] = 9 max_length: ClassVar[int] = 9 def __init__(self, routing_number: str): - self.validate_digits(routing_number) - self._routing_number = self.validate_routing_number(routing_number) + self._validate_digits(routing_number) + self._routing_number = self._validate_routing_number(routing_number) @classmethod def __get_pydantic_core_schema__( cls, source: Type[Any], handler: GetCoreSchemaHandler ) -> core_schema.AfterValidatorFunctionSchema: return core_schema.general_after_validator_function( - cls.validate, + cls._validate, core_schema.str_schema( min_length=cls.min_length, max_length=cls.max_length, @@ -28,20 +52,32 @@ def __get_pydantic_core_schema__( ) @classmethod - def validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ABARoutingNumber': + def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ABARoutingNumber': return cls(__input_value) @classmethod - def validate_digits(cls, routing_number: str) -> None: + def _validate_digits(cls, routing_number: str) -> None: + """Check that the routing number is all digits. + + Args: + routing_number: The routing number to validate. + + Raises: + PydanticCustomError: If the routing number is not all digits. + """ if not routing_number.isdigit(): raise PydanticCustomError('aba_routing_number', 'routing number is not all digits') @classmethod - def validate_routing_number(cls, routing_number: str) -> str: - """ - Check digit algorithm for ABA routing transit number. - https://en.wikipedia.org/wiki/ABA_routing_transit_number#Check_digit - https://www.routingnumber.com/ + def _validate_routing_number(cls, routing_number: str) -> str: + """Check [digit algorithm](https://en.wikipedia.org/wiki/ABA_routing_transit_number#Check_digit) for + [ABA routing transit number](https://www.routingnumber.com/). + + Args: + routing_number: The routing number to validate. + + Raises: + PydanticCustomError: If the routing number is incorrect. """ checksum = ( 3 * (sum(map(int, [routing_number[0], routing_number[3], routing_number[6]]))) diff --git a/pyproject.toml b/pyproject.toml index 6a306de3..388c7764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ all = [ Homepage = 'https://github.com/pydantic/pydantic-extra-types' Source = 'https://github.com/pydantic/pydantic-extra-types' Changelog = 'https://github.com/pydantic/pydantic-extra-types/releases' -Documentation = 'https://docs.pydantic.dev/dev-v2/usage/types/types/' +Documentation = 'https://docs.pydantic.dev/latest/' [tool.ruff] line-length = 120