pydantic

Custom Pydantic types (v1.x).

class yagni.pydantic.types.CaseInsensitiveEnum(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
>>> class Gender(CaseInsensitiveEnum):
...     MALE = 'male'
...     FEMALE = 'female'
>>> Gender('mAlE')
<Gender.MALE: 'male'>
>>> Gender('FEMALE')
<Gender.FEMALE: 'female'>
>>> Gender('helicopter')
Traceback (most recent call last):
...
ValueError: 'helicopter' is not a valid Gender
class yagni.pydantic.types.MBI(*args: Any, **kwargs: Any)

Medicare Beneficiary Identifier (MBI).

https://www.cms.gov/medicare/new-medicare-card/understanding-the-mbi-with-format.pdf

Implementation credits to https://stackoverflow.com/a/47683670/3455614

>>> import re
>>> from pydantic import parse_obj_as
>>> parse_obj_as(MBI, '1ax0Y67Dw34')
'1AX0Y67DW34'
>>> all(re.match(MBI.regex, mbi) for mbi in [
...     '1AX0Y67DW34', '4C56de7FG00', '9EN1EQ3TT59', '2H52CD7GQ83', '3U90VV3UV09',
... ])
True
>>> any(re.match(MBI.regex, mbi) for mbi in ['0AX0Y67DW34', '4256DE7FG00'])
False
class yagni.pydantic.types.NonEmptyStr(*args: Any, **kwargs: Any)

Non-empty string in a shorthand form.

White space is stripped by default.

class yagni.pydantic.types.SSN(*args: Any, **kwargs: Any)

10-digit US SSN with optional dashes.

Dashes are automatically removed during validation.

>>> from pydantic import parse_obj_as
>>> parse_obj_as(SSN, '123456789')
'123456789'
>>> parse_obj_as(SSN, '1234-5-6789')
'123456789'
classmethod cleanup_ssn(value) str

Remove dashes.

class yagni.pydantic.types.StringEnum(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)

Sane enum that behaves like a string.

Can be safely used for JSON serialization. Convenient for API responses and storing in NoSQL or key-value databases.

>>> class Gender(str, Enum):
...     MALE = 'male'
...     FEMALE = 'female'
>>> str(Gender.MALE)
'Gender.MALE'
>>> class Gender(StringEnum):
...     MALE = 'male'
...     FEMALE = 'female'
>>> str(Gender.MALE)
'male'
>>> class Gender(StringEnum):
...     MALE = 'male'
...     FEMALE = 'female'
>>> f'{Gender.MALE}'
'male'
class yagni.pydantic.types.UTCDatetime

Datetime with UTC timezone.

Enforces UTC timezone and disallows naive timestamps.

>>> from pydantic import BaseModel
>>> import pytz
>>> class Message(BaseModel):
...     text: str
...     created: UTCDatetime
>>> naive_str = '1991-08-24 10:00:00'
>>> naive = datetime.fromisoformat(naive_str)
>>> Message(text='Do the barrel roll!', created=naive)
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Message
created
  Naive timestamps are not allowed (type=value_error)
>>> Message(text='Do the barrel roll!', created=naive_str)
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Message
created
  Naive timestamps are not allowed (type=value_error)
>>> Message(text='Батько наш Бандера!', created=naive.replace(tzinfo=pytz.timezone('Europe/Kyiv')))
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Message
created
  Timestamp must be in UTC timezone (type=value_error)
>>> Message(text='Батько наш Бандера!', created=f'{naive_str}+03:00')
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Message
created
  Timestamp must be in UTC timezone (type=value_error)
>>> Message(text='Slava Ukraini!', created=naive.replace(tzinfo=timezone.utc))
Message(text='Slava Ukraini!', created=datetime.datetime(1991, 8, 24, 10, 0, tzinfo=datetime.timezone.utc))
>>> Message(text='Slava Ukraini!', created=f'{naive_str}+00:00')
Message(text='Slava Ukraini!', created=datetime.datetime(1991, 8, 24, 10, 0, tzinfo=datetime.timezone.utc))
>>> Message(text='Slava Ukraini!', created=f'{naive_str}+00:00').json()
'{"text": "Slava Ukraini!", "created": "1991-08-24T10:00:00+00:00"}'