4.5. Dataclass Postinit
Dataclasses generate
__init__()
Overloading
__init__()
manually will destroy itFor init time validation there is
__post_init__()
It is run after all parameters are set in the class
Hence you have to take care about negative cases (errors)
4.5.1. SetUp
>>> from dataclasses import dataclass
>>> from typing import ClassVar
>>> from datetime import date, datetime
4.5.2. Problem
Init serves not only for fields initialization
It could be also used for value validation
>>> class User:
... firstname: str
... lastname: str
... age: int
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
... if self.age < 18:
... raise ValueError('Invalid age')
Usage:
>>> mark = User('Mark', 'Watney', age=42)
>>> vars(mark)
{'firstname': 'Mark', 'lastname': 'Watney', 'age': 42}
>>> mark = User('Mark', 'Watney', age=10)
Traceback (most recent call last):
ValueError: Invalid age
4.5.3. Solution
Creating own
__init__()
will overload init from dataclassesTherefore in dataclasses there is
__post_init__()
methodIt is run after init (as the name suggest)
It works on fields, which already saved (it was done in
__init__
)No need to assign it once again
You can focus only on bailing-out (checking only negative path - errors)
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... age: int
...
... def __post_init__(self):
... if self.age < 18:
... raise ValueError('Invalid age')
Usage:
>>> mark = User('Mark', 'Watney', age=42)
>>> vars(mark)
{'firstname': 'Mark', 'lastname': 'Watney', 'age': 42}
>>> mark = User('Mark', 'Watney', age=10)
Traceback (most recent call last):
ValueError: Invalid age
4.5.4. Type Conversion
__post_init__()
can also be used to convert dataExample str
2000-01-02
to date objectdate(2000, 1, 2)
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... birthdate: str | date
...
... def __post_init__(self):
... if isinstance(self.birthdate, str):
... self.birthdate = date.fromisoformat(self.birthdate)
>>>
>>>
>>> User('Mark', 'Watney', birthdate='2000-01-02')
User(firstname='Mark', lastname='Watney', birthdate=datetime.date(2000, 1, 2))
4.5.5. InitVar
Init-only fields
Added as parameters to the generated
__init__
Passed to the optional
__post_init__
methodThey are not otherwise used by Data Classes
SetUp:
>>> from dataclasses import dataclass, InitVar
Definition:
>>> @dataclass
... class User:
... fullname: InitVar[str]
... firstname: str | None = None
... lastname: str | None = None
...
... def __post_init__(self, fullname):
... if fullname:
... self.firstname, self.lastname = fullname.split()
Usage:
>>> mark = User('Mark Watney')
>>>
>>> mark
User(firstname='Mark', lastname='Watney')
4.5.6. Use Case - 1
>>> from dataclasses import dataclass, KW_ONLY
>>> from typing import ClassVar
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... _: KW_ONLY
... age: int
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
...
... def __post_init__(self):
... if self.age not in range(self.AGE_MIN, self.AGE_MAX):
... raise ValueError
>>>
>>>
>>> mark = User('Mark', 'Watney', age=42)
4.5.7. Use Case - 2
>>> from dataclasses import dataclass, InitVar
>>> from datetime import date, time, datetime, timezone
>>> from zoneinfo import ZoneInfo
>>>
>>>
>>> @dataclass
... class CurrentTime:
... tzname: InitVar[str]
... d: date | None = None
... t: time | None = None
... tz: ZoneInfo | None = None
...
... def __post_init__(self, tzname):
... current = datetime.now(ZoneInfo('UTC'))
... localized = current.astimezone(ZoneInfo(tzname))
... self.d = localized.date()
... self.t = localized.time()
... self.tz = localized.tzname()
Usage:
>>> now = CurrentTime('Europe/Warsaw')
>>> now
CurrentTime(d=datetime.date(1969, 7, 21),
t=datetime.time(2, 56, 15),
tz='CEST')
4.5.8. Use Case - 3
>>> from dataclasses import dataclass, InitVar
>>> import datetime
>>>
>>>
>>> @dataclass
... class DateTime:
... string: InitVar[str]
... date: datetime.date | None = None
... time: datetime.time | None = None
...
... def __post_init__(self, string: str):
... dt = datetime.datetime.fromisoformat(string)
... self.date = dt.date()
... self.time = dt.time()
Usage:
>>> apollo11 = DateTime('1969-07-21 02:56:15')
>>>
>>> apollo11
DateTime(date=datetime.date(1969, 7, 21), time=datetime.time(2, 56, 15))
>>>
>>> apollo11.date
datetime.date(1969, 7, 21)
>>>
>>> apollo11.time
datetime.time(2, 56, 15)
4.5.9. Use Case - 4
SetUp:
>>> from dataclasses import dataclass, field, InitVar
>>> from datetime import date
>>> @dataclass
... class Date:
... year: InitVar[int]
... month: InitVar[int]
... day: InitVar[int]
... current: date = field(default=None, init=False)
...
... def __post_init__(self, year: int, month: int, day: int):
... self.current = date(year, month, day)
Usage:
>>> result = Date(1969, 7, 21)
>>> vars(result)
{'current': datetime.date(1969, 7, 21)}
4.5.10. Use Case - 5
>>> from dataclasses import dataclass, InitVar
>>>
>>>
>>> @dataclass
... class Email:
... address: InitVar[str]
... username: str | None = None
... domain: str | None = None
...
... def __post_init__(self, address):
... self.username, self.domain = address.split('@')
...
... def get_address(self):
... return f'{self.username}@{self.domain}'
Usage:
>>> email = Email('mwatney@nasa.gov')
>>>
>>> print(email)
Email(username='mwatney', domain='nasa.gov')
>>>
>>> print(email.username)
mwatney
>>>
>>> print(email.domain)
nasa.gov
>>>
>>> print(email.get_address())
mwatney@nasa.gov
>>>
>>> print(email.address)
Traceback (most recent call last):
AttributeError: 'Email' object has no attribute 'address'
4.5.11. Use Case - 6
>>> from dataclasses import dataclass
>>> from typing import ClassVar
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
...
... def __post_init__(self):
... min = self.AGE_MIN
... max = self.AGE_MAX
... if self.age not in range(min, max):
... raise ValueError(f'Age {self.age} not in range {min} to {max}')
Usage:
>>> User('Mark', 'Watney', 60)
Traceback (most recent call last):
ValueError: Age 60 not in range 30 to 50
>>> User('Mark', 'Watney', 60, AGE_MAX=70)
Traceback (most recent call last):
TypeError: User.__init__() got an unexpected keyword argument 'AGE_MAX'
4.5.12. Use Case - 7
Boundary check
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Point:
... x: int = 0
... y: int = 0
...
... def __post_init__(self):
... if self.x < 0 or self.y < 0:
... raise ValueError('Coordinate cannot be negative')
4.5.13. Use Case - 8
Var Range
>>> from dataclasses import dataclass, field
>>> from typing import Final
>>>
>>>
>>> @dataclass
... class Point:
... x: int = 0
... y: int = 0
... X_MIN: Final[int] = 0
... X_MAX: Final[int] = 1024
... Y_MIN: Final[int] = 0
... Y_MAX: Final[int] = 768
...
... def __post_init__(self):
... if not self.X_MIN <= self.x < self.X_MAX:
... raise ValueError(f'x value ({self.x}) is not between {self.X_MIN} and {self.X_MAX}')
... if not self.Y_MIN <= self.y < self.Y_MAX:
... raise ValueError(f'y value ({self.y}) is not between {self.Y_MIN} and {self.Y_MAX}')
Usage:
>>> Point(0, 0)
Point(x=0, y=0, X_MIN=0, X_MAX=1024, Y_MIN=0, Y_MAX=768)
>>> Point(-1, 0)
Traceback (most recent call last):
ValueError: x value (-1) is not between 0 and 1024
>>> Point(0, 2000)
Traceback (most recent call last):
ValueError: y value (2000) is not between 0 and 768
>>> Point(0, 0, X_MIN=10, X_MAX=100)
Traceback (most recent call last):
ValueError: x value (0) is not between 10 and 100
4.5.14. Use Case - 9
Const Range
>>> from dataclasses import dataclass, field
>>> from typing import Final
>>>
>>>
>>> @dataclass
... class Point:
... x: int = 0
... y: int = 0
... X_MIN: Final[int] = field(init=False, default=0)
... X_MAX: Final[int] = field(init=False, default=1024)
... Y_MIN: Final[int] = field(init=False, default=0)
... Y_MAX: Final[int] = field(init=False, default=768)
...
... def __post_init__(self):
... if not self.X_MIN <= self.x < self.X_MAX:
... raise ValueError(f'x value ({self.x}) is not between {self.X_MIN} and {self.X_MAX}')
... if not self.Y_MIN <= self.y < self.Y_MAX:
... raise ValueError(f'y value ({self.y}) is not between {self.Y_MIN} and {self.Y_MAX}')
Usage:
>>> Point(0, 0)
Point(x=0, y=0, X_MIN=0, X_MAX=1024, Y_MIN=0, Y_MAX=768)
>>> Point(0, 0, X_MIN=10, X_MAX=100)
Traceback (most recent call last):
TypeError: Point.__init__() got an unexpected keyword argument 'X_MIN'
4.5.15. Use Case - 10
Init, Repr
>>> from dataclasses import dataclass, field
>>> from typing import Final
>>>
>>>
>>> @dataclass
... class Point:
... x: int = 0
... y: int = 0
... X_MIN: Final[int] = field(init=False, repr=False, default=0)
... X_MAX: Final[int] = field(init=False, repr=False, default=1024)
... Y_MIN: Final[int] = field(init=False, repr=False, default=0)
... Y_MAX: Final[int] = field(init=False, repr=False, default=768)
...
... def __post_init__(self):
... if not self.X_MIN <= self.x < self.X_MAX:
... raise ValueError(f'x value ({self.x}) is not between {self.X_MIN} and {self.X_MAX}')
... if not self.Y_MIN <= self.y < self.Y_MAX:
... raise ValueError(f'y value ({self.y}) is not between {self.Y_MIN} and {self.Y_MAX}')
Usage:
>>> Point(0, 0)
Point(x=0, y=0)
>>> Point(-1, 0)
Traceback (most recent call last):
ValueError: x value (-1) is not between 0 and 1024
>>> Point(0, -1)
Traceback (most recent call last):
ValueError: y value (-1) is not between 0 and 768
4.5.16. Use Case - 11
>>> from dataclasses import dataclass, InitVar
>>>
>>> @dataclass
... class Phone:
... full_number: InitVar[str]
...
... country_code: int = None
... number: int = None
...
... def __post_init__(self, full_number: str):
... self.country_code, self.number = full_number.split(' ', maxsplit=1)
Usage:
>>> phone = Phone('+48 123 456 789')
>>> phone
Phone(country_code='+48', number='123 456 789')
4.5.17. Use Case - 12
>>> from dataclasses import dataclass, InitVar
>>> from datetime import datetime
>>>
>>>
>>> @dataclass
... class Pesel:
... number: InitVar[str]
...
... pesel: str = None
... birthdate: str = None
... gender: str = None
... valid: bool = None
...
... def calc_check_digit(self):
... weights = (1, 3, 7, 9, 1, 3, 7, 9, 1, 3)
... check = sum(w * int(n) for w, n in zip(weights, self.pesel))
... return str((10 - check) % 10)
...
... def __post_init__(self, number: str):
... self.pesel = number
... self.birthdate = datetime.strptime(number[:6], '%y%m%d').date()
... self.gender = 'male' if int(number[-2]) % 2 else 'female'
... self.valid = number[-1] == self.calc_check_digit()
Usage:
>>> pesel = Pesel('69072101234')
>>> pesel
Pesel(pesel='69072101234',
birthdate=datetime.date(1969, 7, 21),
gender='male',
valid=False)
4.5.18. Use Case - 13
>>> from dataclasses import dataclass, InitVar, field
>>> from datetime import date
>>> from hashlib import sha256
>>> from typing import ClassVar
>>>
>>>
>>> @dataclass(kw_only=True, slots=True)
... class User:
... firstname: str
... lastname: str
... plaintext_password: InitVar[str]
... username: str | None = None
... password: str | None = field(default=None, repr=False, init=False)
... age: int | None = None
... weight: float | None = None
... birthdate: str | date | None = None
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
...
... def __post_init__(self, plaintext_password):
... self.password = sha256(plaintext_password.encode()).hexdigest()
... if isinstance(self.birthdate, str):
... self.birthdate = date.fromisoformat(self.birthdate)
... if not self.AGE_MIN <= self.age <= self.AGE_MAX:
... raise ValueError('Invalid age')
Create an object:
>>> mark = User(
... firstname='Mark',
... lastname='Watney',
... username='mwatney',
... plaintext_password='Ares3',
... age=40,
... weight=56,
... birthdate='1969-07-21',
... )
>>> mark
User(firstname='Mark', lastname='Watney', username='mwatney', age=40, weight=56, birthdate=datetime.date(1969, 7, 21))
>>> mark.password
'31a7f50d4096c10f7d95fec5f8940ed7142a5c582b38551af49b0533fa97d407'
4.5.19. Use Case - 14
>>> import re
>>>
>>> @dataclass
... class EmailValidator:
... domain: InitVar[str]
... pattern: re.Pattern = None
...
... def __post_init__(self, domain: str):
... self.pattern = re.compile(fr'^[a-z]+@{domain}$')
...
... def is_valid(self, email: str) -> bool:
... return bool(self.pattern.match(email))
Usage:
>>> email = EmailValidator(domain='nasa.gov')
>>>
>>> email.is_valid('mwatney@nasa.gov')
True
>>>
>>> email.is_valid('mwatney@nasa.gov.pl')
False
Rationale:
>>> email
EmailValidator(pattern=re.compile('^[a-z]+@nasa.gov$'))
>>>
>>> hasattr(email, 'domain')
False
4.5.20. Use Case - 15
>>> from dataclasses import InitVar
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... email: InitVar[str]
... username: str | None = None
...
... def __post_init__(self, email: str):
... self.username = email.split('@')[0]
>>>
>>> mark = User('Mark', 'Watney', 'mwatney@nasa.gov')
4.5.21. Use Case - 16
>>> @dataclass
... class Validator:
... regex: InitVar[str] = ''
... pattern: re.Pattern | None = None
...
... def __post_init__(self, regex: str):
... self.pattern = re.compile(regex)
...
... def match(self, string: str):
... return self.pattern.match(string)
...
...
>>> validator = Validator(r'^[a-z]+$')
>>> validator.match('mwatney@nasa.gov')
4.5.22. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Dataclass PostInit Syntax
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% English
# 1. Use Dataclass to define class `Point` with attributes:
# - `x: int` with default value `0`
# - `y: int` with default value `0`
# 2. When `x` or `y` has negative value raise en exception `ValueError('Coordinate cannot be negative')`
# 3. Use `datalass` and validation in `__post_init__()`
# 4. Run doctests - all must succeed
# %% Polish
# 1. Użyj Dataclass do zdefiniowania klasy `Point` z atrybutami:
# - `x: int` z domyślną wartością `0`
# - `y: int` z domyślną wartością `0`
# 2. Gdy `x` lub `y` mają wartość ujemną podnieś wyjątek `ValueError('Coordinate cannot be negative')`
# 3. Użyj `datalass` i walidacji w `__post_init__()`
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `raise ValueError('error message')`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isclass
>>> from dataclasses import is_dataclass
>>> assert isclass(Point)
>>> assert is_dataclass(Point)
>>> assert hasattr(Point, 'x')
>>> assert hasattr(Point, 'y')
>>> Point()
Point(x=0, y=0)
>>> Point(x=0, y=0)
Point(x=0, y=0)
>>> Point(x=1, y=2)
Point(x=1, y=2)
>>> Point(x=-1, y=0)
Traceback (most recent call last):
ValueError: Coordinate cannot be negative
>>> Point(x=0, y=-1)
Traceback (most recent call last):
ValueError: Coordinate cannot be negative
"""
from dataclasses import dataclass
# Use Dataclass to define class `Point` with attributes: `x` and `y`
# type: type
@dataclass
class Point:
x: int = 0
y: int = 0
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Dataclass PostInit DatabaseDump
# - Difficulty: medium
# - Lines: 5
# - Minutes: 5
# %% English
# 1. You received input data in JSON format from the API
# - `str` fields: firstname, lastname, role, username, password, email,
# - `date` field: birthdate,
# - `datetime` field: last_login (optional),
# - `bool` fields: is_active, is_staff, is_superuser,
# - `list[dict]` field: user_permissions
# 2. Parse fields with dates and store as `date` or `datetime` objects
# 3. Use `__post_init__()`
# 4. Run doctests - all must succeed
# %% Polish
# 1. Otrzymałeś z API dane wejściowe w formacie JSON
# - pola `str`: firstname, lastname, role, username, password, email,
# - pole `date`: birthdate,
# - pole `datetime`: last_login (opcjonalne),
# - pola `bool`: is_active, is_staff, is_superuser,
# - pola `list[dict]`: user_permissions
# 2. Sparsuj pola z datami i zapisz je jako obiekty `date` lub `datetime`
# 3. Użyj `__post_init__()`
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `birthdate: date`
# - `last_login: datetime | None`
# - `date.fromisoformat(...)`
# - `datetime.fromisoformat(...)`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from inspect import isclass
>>> from dataclasses import is_dataclass
>>> assert isclass(User)
>>> assert is_dataclass(User)
>>> attributes = User.__dataclass_fields__.keys()
>>> list(attributes) # doctest: +NORMALIZE_WHITESPACE
['firstname', 'lastname', 'role', 'username', 'password', 'email', 'birthdate',
'last_login', 'is_active', 'is_staff', 'is_superuser', 'user_permissions']
>>> data = json.loads(DATA)
>>> result = [User(**user['fields']) for user in data]
>>> last_login = [user['fields']['last_login'] for user in data]
>>> last_login # doctest: +NORMALIZE_WHITESPACE
['1970-01-01T00:00:00.000+00:00',
None,
None,
'1970-01-01T00:00:00.000+00:00',
None,
None]
>>> last_login = [user.last_login for user in result]
>>> last_login # doctest: +NORMALIZE_WHITESPACE
[datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
None,
None,
datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
None,
None]
>>> birthdate = {user['fields']['birthdate'] for user in data}
>>> sorted(birthdate) # doctest: +NORMALIZE_WHITESPACE
['1994-10-12',
'1994-11-15',
'1995-07-15',
'1996-01-21',
'1999-08-02',
'2006-05-09']
>>> birthdate = {user.birthdate for user in result}
>>> sorted(birthdate) # doctest: +NORMALIZE_WHITESPACE
[datetime.date(1994, 10, 12),
datetime.date(1994, 11, 15),
datetime.date(1995, 7, 15),
datetime.date(1996, 1, 21),
datetime.date(1999, 8, 2),
datetime.date(2006, 5, 9)]
>>> result[0] # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
User(firstname='Melissa',
lastname='Lewis',
role='commander',
username='mlewis',
password='pbkdf2_sha256$120000$gvEBNiCeTrYa0$5C+NiCeTrYsha1PHog...=',
email='mlewis@nasa.gov',
birthdate=datetime.date(1995, 7, 15),
last_login=datetime.datetime(1970, 1, 1, 0, 0,
tzinfo=datetime.timezone.utc),
is_active=True,
is_staff=True,
is_superuser=False,
user_permissions=[{'eclss': ['add', 'modify', 'view']},
{'communication': ['add', 'modify', 'view']},
{'medical': ['add', 'modify', 'view']},
{'science': ['add', 'modify', 'view']}])
>>> result[1] # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
User(firstname='Rick',
lastname='Martinez',
role='pilot',
username='rmartinez',
password='pbkdf2_sha256$120000$aXNiCeTrY$UfCJrBh/qhXohNiCeTrYH8...=',
email='rmartinez@nasa.gov',
birthdate=datetime.date(1996, 1, 21),
last_login=None,
is_active=True,
is_staff=True,
is_superuser=False,
user_permissions=[{'communication': ['add', 'view']},
{'eclss': ['add', 'modify', 'view']},
{'science': ['add', 'modify', 'view']}])
"""
import json
from dataclasses import dataclass
from datetime import date, datetime
DATA = (
'[{"model":"authorization.user","pk":1,"fields":{"firstname":"Meli'
'ssa","lastname":"Lewis","role":"commander","username":"mlewis","p'
'assword":"pbkdf2_sha256$120000$gvEBNiCeTrYa0$5C+NiCeTrYsha1PHogqv'
'XNiCeTrY0CRSLYYAA90=","email":"mlewis@nasa.gov","birthdate":"1995-'
'07-15","last_login":"1970-01-01T00:00:00.000+00:00","is_active":t'
'rue,"is_staff":true,"is_superuser":false,"user_permissions":[{"ec'
'lss":["add","modify","view"]},{"communication":["add","modify","v'
'iew"]},{"medical":["add","modify","view"]},{"science":["add","mod'
'ify","view"]}]}},{"model":"authorization.user","pk":2,"fields":{"'
'firstname":"Rick","lastname":"Martinez","role":"pilot","username"'
':"rmartinez","password":"pbkdf2_sha256$120000$aXNiCeTrY$UfCJrBh/q'
'hXohNiCeTrYH8nsdANiCeTrYnShs9M/c=","birthdate":"1996-01-21","last_'
'login":null,"email":"rmartinez@nasa.gov","is_active":true,"is_sta'
'ff":true,"is_superuser":false,"user_permissions":[{"communication'
'":["add","view"]},{"eclss":["add","modify","view"]},{"science":["'
'add","modify","view"]}]}},{"model":"authorization.user","pk":3,"f'
'ields":{"firstname":"Alex","lastname":"Vogel","role":"chemist","u'
'sername":"avogel","password":"pbkdf2_sha256$120000$eUNiCeTrYHoh$X'
'32NiCeTrYZOWFdBcVT1l3NiCeTrY4WJVhr+cKg=","email":"avogel@esa.int"'
',"birthdate":"1994-11-15","last_login":null,"is_active":true,"is_s'
'taff":true,"is_superuser":false,"user_permissions":[{"eclss":["ad'
'd","modify","view"]},{"communication":["add","modify","view"]},{"'
'medical":["add","modify","view"]},{"science":["add","modify","vie'
'w"]}]}},{"model":"authorization.user","pk":4,"fields":{"firstname'
'":"Chris","lastname":"Beck","role":"crew-medical-officer","userna'
'me":"cbeck","password":"pbkdf2_sha256$120000$3G0RNiCeTrYlaV1$mVb6'
'2WNiCeTrYQ9aYzTsSh74NiCeTrY2+c9/M=","email":"cbeck@nasa.gov","bir'
'thdate":"1999-08-02","last_login":"1970-01-01T00:00:00.000+00:00",'
'"is_active":true,"is_staff":true,"is_superuser":false,"user_permi'
'ssions":[{"communication":["add","view"]},{"medical":["add","modi'
'fy","view"]},{"science":["add","modify","view"]}]}},{"model":"aut'
'horization.user","pk":5,"fields":{"firstname":"Beth","lastname":"'
'Johanssen","role":"sysop","username":"bjohanssen","password":"pbk'
'df2_sha256$120000$QmSNiCeTrYBv$Nt1jhVyacNiCeTrYSuKzJ//WdyjlNiCeTr'
'YYZ3sB1r0g=","email":"bjohanssen@nasa.gov","birthdate":"2006-05-09'
'","last_login":null,"is_active":true,"is_staff":true,"is_superuse'
'r":false,"user_permissions":[{"communication":["add","view"]},{"s'
'cience":["add","modify","view"]}]}},{"model":"authorization.user"'
',"pk":6,"fields":{"firstname":"Mark","lastname":"Watney","role":"'
'botanist","username":"mwatney","password":"pbkdf2_sha256$120000$b'
'xS4dNiCeTrY1n$Y8NiCeTrYRMa5bNJhTFjNiCeTrYp5swZni2RQbs=","email":"'
'mwatney@nasa.gov","birthdate":"1994-10-12","last_login":null,"is_a'
'ctive":true,"is_staff":true,"is_superuser":false,"user_permission'
's":[{"communication":["add","modify","view"]},{"science":["add","'
'modify","view"]}]}}]'
)
# Using `dataclass` model data as class `User`
# type: type
@dataclass
class User:
firstname: str
lastname: str
role: str
username: str
password: str
email: str
birthdate: date
last_login: datetime | None
is_active: bool
is_staff: bool
is_superuser: bool
user_permissions: list[dict]