Skip to content

Working with Pydantic

If you want to work with ISO Week (date) format within pydantic v2, i.e. create a model with a string field representing an ISO Week (date) format, there are two options: the easy way and the proper way.

Both require the pydantic extra, see installation:

python -m pip install "iso-week-date[pydantic]"

Every output on this page is generated by running the code shown.

The easy way

The easy way to achieve this is via Annotated and StringConstraints with custom regex patterns.

The regex patterns are available in the top level module of iso-week-date, therefore it is possible to use them directly:

from typing import Annotated

from pydantic import BaseModel, StringConstraints

from iso_week_date import ISOWEEK_PATTERN, ISOWEEKDATE_PATTERN

T_ISOWeekStr = Annotated[str, StringConstraints(pattern=ISOWEEK_PATTERN.pattern)]
T_ISOWeekDateStr = Annotated[
    str, StringConstraints(pattern=ISOWEEKDATE_PATTERN.pattern)
]


class MyModel(BaseModel):
    week: T_ISOWeekStr
    week_date: T_ISOWeekDateStr


print(MyModel(week="2023-W01", week_date="2023-W01-1"))
print(
    MyModel(week="2023-W53", week_date="2023-W01-1")
)  # accepted, but 2023 has no week 53
week='2023-W01' week_date='2023-W01-1'
week='2023-W53' week_date='2023-W01-1'

Caveat

The caveat of this approach can be seen in the second instance in the example above. Namely the regex patterns could be not strict enough for your purposes, i.e. they allow for some combinations that are not valid ISO Week (date) formats.

In fact not every combination of year and week number should be possible (not every year has 53 weeks!), but this is not enforced by the regex patterns.

Note

Remark that actual validation happens when instantiating IsoWeek and IsoWeekDate classes. See why validation is stricter than the regex for the reasoning.

What happens to such a value downstream depends on your Python version, which is its own reason not to let it through:

import sys
from datetime import datetime

print(f"python {sys.version_info.major}.{sys.version_info.minor}")

# 2023 has 52 weeks
try:
    print(datetime.strptime("2023-W53-1", "%G-W%V-%u"))
except ValueError as e:
    print(f"ValueError: {e}")

print(datetime.strptime("2024-W01-1", "%G-W%V-%u"))
python 3.14
ValueError: Invalid week: 53
2024-01-01 00:00:00

Up to Python 3.11, strptime accepted 2023-W53-1 and silently normalized it to the same datetime as 2024-W01-1, turning an impossible week into a plausible date a year off. Since Python 3.12 it raises ValueError instead. Neither outcome is one you want to discover in production, so it is better for the model field to reject the value at the boundary.

The proper way

The pydantic submodule implements the T_ISOWeek and T_ISOWeekDate custom types using custom validation with __get_pydantic_core_schema__, which run the same validation as the classes themselves. It requires pydantic>=2.4.0.

from pydantic import BaseModel, ValidationError

from iso_week_date.pydantic import T_ISOWeek, T_ISOWeekDate


class MyModel(BaseModel):
    week: T_ISOWeek
    week_date: T_ISOWeekDate


print(MyModel(week="2023-W01", week_date="2023-W01-1"))  # All good here!

try:
    MyModel(week="2023-W53", week_date="2023-W01-1")
except ValidationError as e:
    print(e)
week='2023-W01' week_date='2023-W01-1'
1 validation error for MyModel
week
  Invalid week number. Year 2023 has only 52 weeks. [type=T_ISOWeek, input_value='2023-W53', input_type=str]

Which one to use

  • Use the regex types when you are validating a shape at a boundary, in volume, and a week number that never existed is an acceptable risk.
  • Use the iso_week_date.pydantic types when the value has to be a week that really existed. This is the default recommendation.

Compact formats

The compact formats (YYYYWNN, YYYYWNND) are not directly available in the module. However if needed it is possible to compose them from the building blocks:

from typing import Final

from iso_week_date._patterns import (  # These are strings, not compiled patterns
    WEEK_MATCH,
    WEEKDAY_MATCH,
    YEAR_MATCH,
)

ISOWEEK_COMPACT_PATTERN: Final[str] = rf"^{YEAR_MATCH}{WEEK_MATCH}$"
ISOWEEKDATE_COMPACT_PATTERN: Final[str] = rf"^{YEAR_MATCH}{WEEK_MATCH}{WEEKDAY_MATCH}$"

T_ISOWeekCompact = Annotated[str, StringConstraints(pattern=ISOWEEK_COMPACT_PATTERN)]
T_ISOWeekDateCompact = Annotated[
    str, StringConstraints(pattern=ISOWEEKDATE_COMPACT_PATTERN)
]


class CompactModel(BaseModel):
    week: T_ISOWeekCompact
    week_date: T_ISOWeekDateCompact


print(CompactModel(week="2023W01", week_date="2023W011"))
week='2023W01' week_date='2023W011'

_patterns is a private module

iso_week_date._patterns is private: its contents may change or move without a deprecation cycle. If you depend on it, pin the version and cover it with a test. For validation that is guaranteed to keep working, parse with IsoWeek.from_compact instead.