sysnet-persons-model 1.4.2__tar.gz → 1.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/PKG-INFO +5 -1
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/README.md +3 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/__init__.py +6 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/common.py +43 -3
- sysnet_persons_model-1.6.0/persons_model/dates.py +87 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/individual.py +13 -2
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/person_entity.py +33 -3
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/pyproject.toml +2 -1
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/PKG-INFO +5 -1
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/SOURCES.txt +2 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/requires.txt +1 -0
- sysnet_persons_model-1.6.0/tests/test_birth_date.py +155 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_person_entity.py +45 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/LICENSE +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/config.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/context.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/country.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/legal_form.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/role.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/persons_model/tag.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/requirements.txt +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/setup.cfg +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/dependency_links.txt +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/top_level.txt +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_common.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_config.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_context.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_country.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_individual.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_legal_form.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_role.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_scope.py +0 -0
- {sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/tests/test_tag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-persons-model
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: SYSNET Persons Data Model
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
License-Expression: LicenseRef-SYSNET-FreeForHomeUse
|
|
@@ -18,12 +18,15 @@ Requires-Dist: sysnet-pyutils>=1.7.0
|
|
|
18
18
|
Requires-Dist: pydantic[email]>=2.11.4
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pytest>=9.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.26; extra == "dev"
|
|
21
22
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
22
23
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# sysnet-persons-model
|
|
26
27
|
|
|
28
|
+

|
|
29
|
+
|
|
27
30
|
Data model pro entity a osoby v ekosystému SYSNET. Postaveno na Pydantic v2.
|
|
28
31
|
|
|
29
32
|
## Instalace
|
|
@@ -39,6 +42,7 @@ pip install sysnet-persons-model
|
|
|
39
42
|
- **Opaque extension slot** (`agenda`) pro agendově specifická data.
|
|
40
43
|
|
|
41
44
|
## Verze
|
|
45
|
+
- **1.4.2**: Oprava validace `ScopeType` — rozsah `GLOBAL` s neprázdným seznamem jurisdikcí nyní vyvolá Pydantic `ValidationError` se zprávou „Globální rozsah nesmí definovat jurisdikce.“ místo jejich tichého ignorování. Sjednocení souboru `VERSION` na `1.4.2` a přidání `pytest-asyncio>=0.26` do vývojových závislostí. Viz [CHANGELOG.md](CHANGELOG.md).
|
|
42
46
|
- **1.3.0**: Přidání atributu `agenda` (opaque extension slot) do `IndividualBaseType` a `PersonEntityBaseType`. Viz [AGENDA_GUIDE.md](docs/AGENDA_GUIDE.md).
|
|
43
47
|
- **1.2.8**: Interní verze, přidání atributu `agenda` (špatná cesta k docs).
|
|
44
48
|
- **1.2.7**: Přidání aliasu `metadata` pro pole `document` (Redmine #9850).
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# sysnet-persons-model
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Data model pro entity a osoby v ekosystému SYSNET. Postaveno na Pydantic v2.
|
|
4
6
|
|
|
5
7
|
## Instalace
|
|
@@ -15,6 +17,7 @@ pip install sysnet-persons-model
|
|
|
15
17
|
- **Opaque extension slot** (`agenda`) pro agendově specifická data.
|
|
16
18
|
|
|
17
19
|
## Verze
|
|
20
|
+
- **1.4.2**: Oprava validace `ScopeType` — rozsah `GLOBAL` s neprázdným seznamem jurisdikcí nyní vyvolá Pydantic `ValidationError` se zprávou „Globální rozsah nesmí definovat jurisdikce.“ místo jejich tichého ignorování. Sjednocení souboru `VERSION` na `1.4.2` a přidání `pytest-asyncio>=0.26` do vývojových závislostí. Viz [CHANGELOG.md](CHANGELOG.md).
|
|
18
21
|
- **1.3.0**: Přidání atributu `agenda` (opaque extension slot) do `IndividualBaseType` a `PersonEntityBaseType`. Viz [AGENDA_GUIDE.md](docs/AGENDA_GUIDE.md).
|
|
19
22
|
- **1.2.8**: Interní verze, přidání atributu `agenda` (špatná cesta k docs).
|
|
20
23
|
- **1.2.7**: Přidání aliasu `metadata` pro pole `document` (Redmine #9850).
|
|
@@ -4,6 +4,7 @@ from persons_model.common import (
|
|
|
4
4
|
RedundantType,
|
|
5
5
|
DepartmentType,
|
|
6
6
|
ContactType,
|
|
7
|
+
ContactEntryType,
|
|
7
8
|
MembershipEnum,
|
|
8
9
|
MemberType,
|
|
9
10
|
IssuingType,
|
|
@@ -20,7 +21,9 @@ from persons_model.legal_form import (
|
|
|
20
21
|
from persons_model.person_entity import (
|
|
21
22
|
PersonEntityLinkType,
|
|
22
23
|
PersonEntityEntryType,
|
|
24
|
+
PersonEntityExtEntryType,
|
|
23
25
|
PersonEntityListType,
|
|
26
|
+
PersonEntityExtListType,
|
|
24
27
|
PersonEntityBaseType,
|
|
25
28
|
PersonEntityType,
|
|
26
29
|
)
|
|
@@ -34,6 +37,7 @@ __all__ = [
|
|
|
34
37
|
'RedundantType',
|
|
35
38
|
'DepartmentType',
|
|
36
39
|
'ContactType',
|
|
40
|
+
'ContactEntryType',
|
|
37
41
|
'MembershipEnum',
|
|
38
42
|
'MemberType',
|
|
39
43
|
'IssuingType',
|
|
@@ -58,7 +62,9 @@ __all__ = [
|
|
|
58
62
|
# person_entity
|
|
59
63
|
'PersonEntityLinkType',
|
|
60
64
|
'PersonEntityEntryType',
|
|
65
|
+
'PersonEntityExtEntryType',
|
|
61
66
|
'PersonEntityListType',
|
|
67
|
+
'PersonEntityExtListType',
|
|
62
68
|
'PersonEntityBaseType',
|
|
63
69
|
'PersonEntityType',
|
|
64
70
|
# role
|
|
@@ -3,9 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from datetime import date
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, StrictBool, field_validator, model_validator
|
|
7
9
|
from sysnet_pyutils.models.general import LocationType, PhoneNumberType, MailAddressType, BaseEnum
|
|
8
10
|
|
|
11
|
+
from persons_model.dates import PureDate, sync_birth_fields
|
|
9
12
|
from persons_model.tag import TagType
|
|
10
13
|
|
|
11
14
|
|
|
@@ -55,7 +58,39 @@ class ContactType(BaseModel):
|
|
|
55
58
|
degree_before: Optional[List[Optional[str]]] = Field(default=None, description="Tituly před jménem")
|
|
56
59
|
degree_after: Optional[List[Optional[str]]] = Field(default=None, description="Tituly za jménem")
|
|
57
60
|
full_name: Optional[str] = Field(default=None, description="Celé jméno pro zobrazení a tisk")
|
|
58
|
-
birthdate: Optional[datetime] = Field(
|
|
61
|
+
birthdate: Optional[datetime] = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description="Datum narození jako okamžik (zastaralé, ponecháno kvůli zpětné kompatibilitě). "
|
|
64
|
+
"Použijte birth_date.", json_schema_extra={'deprecated': True})
|
|
65
|
+
birth_date: Optional[PureDate] = Field(
|
|
66
|
+
default=None, description="Datum narození jako čisté datum (YYYY-MM-DD, bez času a zóny)")
|
|
67
|
+
phone: Optional[List[Optional[PhoneNumberType]]] = Field(default=None, description="Telefonní číslo")
|
|
68
|
+
phone_default: Optional[int] = Field(default=0, description="Index hlavního telefonního čísla", alias="phoneDefault")
|
|
69
|
+
email: Optional[List[Optional[MailAddressType]]] = Field(default=None, description="Elektronická pošta")
|
|
70
|
+
email_default: Optional[int] = Field(default=0, description="Index hlavní adresy elektronické pošty")
|
|
71
|
+
web: Optional[str] = Field(default=None, description="Webová stránka subjektu")
|
|
72
|
+
isds: Optional[str] = Field(default=None, description="Identifikátor datové schránky")
|
|
73
|
+
reference: Optional[str] = Field(default=None, description="identifikátor uuid")
|
|
74
|
+
|
|
75
|
+
@model_validator(mode='before')
|
|
76
|
+
@classmethod
|
|
77
|
+
def _sync_birth_fields(cls, data):
|
|
78
|
+
return sync_birth_fields(data)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ContactEntryType(BaseModel):
|
|
82
|
+
"""
|
|
83
|
+
Kontaktní údaje ve výpisu (``GET /person?extended=true``).
|
|
84
|
+
|
|
85
|
+
Stejná pole a stejné aliasy jako ``ContactType``, **bez** ``birthdate`` — datum narození zástupce
|
|
86
|
+
nepatří do hromadného výpisu (minimalizace osobních údajů); je dostupné jen v detailu subjektu.
|
|
87
|
+
"""
|
|
88
|
+
identifier: Optional[str] = Field(default=None, description="identifikátor uuid")
|
|
89
|
+
first_name: Optional[str] = Field(default=None, description="Křestní jméno")
|
|
90
|
+
last_name: Optional[str] = Field(default=None, description="Příjmení")
|
|
91
|
+
degree_before: Optional[List[Optional[str]]] = Field(default=None, description="Tituly před jménem")
|
|
92
|
+
degree_after: Optional[List[Optional[str]]] = Field(default=None, description="Tituly za jménem")
|
|
93
|
+
full_name: Optional[str] = Field(default=None, description="Celé jméno pro zobrazení a tisk")
|
|
59
94
|
phone: Optional[List[Optional[PhoneNumberType]]] = Field(default=None, description="Telefonní číslo")
|
|
60
95
|
phone_default: Optional[int] = Field(default=0, description="Index hlavního telefonního čísla", alias="phoneDefault")
|
|
61
96
|
email: Optional[List[Optional[MailAddressType]]] = Field(default=None, description="Elektronická pošta")
|
|
@@ -130,8 +165,13 @@ class ScopeType(BaseModel):
|
|
|
130
165
|
def validate_jurisdictions(cls, jurisdictions, info):
|
|
131
166
|
level = info.data.get("level")
|
|
132
167
|
|
|
168
|
+
if level == ScopeLevelEnum.GLOBAL:
|
|
169
|
+
if jurisdictions:
|
|
170
|
+
raise ValueError("Globální rozsah nesmí definovat jurisdikce.")
|
|
171
|
+
return None
|
|
172
|
+
|
|
133
173
|
# Úrovně s implicitním celým rozsahem — jurisdikce se nevyžadují ani nezakazují
|
|
134
|
-
BROAD_LEVELS = {ScopeLevelEnum.
|
|
174
|
+
BROAD_LEVELS = {ScopeLevelEnum.NATIONAL, ScopeLevelEnum.CONTINENTAL}
|
|
135
175
|
|
|
136
176
|
if level in BROAD_LEVELS:
|
|
137
177
|
# Prázdné jurisdikce znamenají „celý rozsah dané úrovně" — jsou v pořádku
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Převod hodnot data narození na kalendářní datum (Redmine #10334).
|
|
3
|
+
|
|
4
|
+
Registr osob dosud ukládal datum narození jako ``datetime`` (naivní, většinou půlnoc UTC). Klienti ale
|
|
5
|
+
posílali různé tvary (``2000-01-02``, ``…T12:00:00``, ``…T22:00:00Z``), takže se den při převodech mezi
|
|
6
|
+
Prahou a UTC posouval. Kalendářní datum zóny nemá; jediné pravidlo je zde:
|
|
7
|
+
|
|
8
|
+
- hodnota **se zónou** (``Z``, ``+00:00``, …) → den v ``Europe/Prague``,
|
|
9
|
+
- hodnota **bez zóny** (``date``, naivní ``datetime``, řetězec bez zóny) → den tak, jak je.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from datetime import date, datetime
|
|
14
|
+
from typing import Annotated, Any, Optional
|
|
15
|
+
from zoneinfo import ZoneInfo
|
|
16
|
+
|
|
17
|
+
from pydantic import BeforeValidator
|
|
18
|
+
|
|
19
|
+
PRAGUE = ZoneInfo("Europe/Prague")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def to_calendar_date(value: Any) -> Optional[date]:
|
|
23
|
+
"""Vrátí kalendářní datum z ``date``, ``datetime`` nebo ISO řetězce; ``None`` zůstane ``None``.
|
|
24
|
+
|
|
25
|
+
:raises ValueError: pokud hodnotu nelze přečíst jako datum.
|
|
26
|
+
"""
|
|
27
|
+
if value is None or value == '':
|
|
28
|
+
return None
|
|
29
|
+
if isinstance(value, datetime):
|
|
30
|
+
if value.tzinfo is None:
|
|
31
|
+
return value.date()
|
|
32
|
+
try:
|
|
33
|
+
return value.astimezone(PRAGUE).date()
|
|
34
|
+
except OverflowError:
|
|
35
|
+
# okraj rozsahu (0001-01-01 / 9999-12-31): posun o zónu by přetekl, bereme den tak, jak je
|
|
36
|
+
return value.date()
|
|
37
|
+
if isinstance(value, date):
|
|
38
|
+
return value
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
text = value.strip()
|
|
41
|
+
if text.endswith(('Z', 'z')):
|
|
42
|
+
text = text[:-1] + '+00:00'
|
|
43
|
+
if 'T' in text or ' ' in text:
|
|
44
|
+
try:
|
|
45
|
+
parsed = datetime.fromisoformat(text)
|
|
46
|
+
except ValueError as exc:
|
|
47
|
+
raise ValueError(f"Neplatné datum: {value!r}") from exc
|
|
48
|
+
return to_calendar_date(parsed)
|
|
49
|
+
try:
|
|
50
|
+
return date.fromisoformat(text)
|
|
51
|
+
except ValueError as exc:
|
|
52
|
+
raise ValueError(f"Neplatné datum: {value!r}") from exc
|
|
53
|
+
raise ValueError(f"Neplatné datum: {value!r}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def to_midnight(value: Optional[date]) -> Optional[datetime]:
|
|
57
|
+
"""Půlnoc daného dne jako naivní ``datetime`` (kompatibilní tvar pro starší pole ``birthdate``)."""
|
|
58
|
+
if value is None:
|
|
59
|
+
return None
|
|
60
|
+
return datetime(value.year, value.month, value.day)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
PureDate = Annotated[date, BeforeValidator(to_calendar_date)]
|
|
64
|
+
"""Kalendářní datum: ``date`` v modelu, ``YYYY-MM-DD`` v JSON, ``format: date`` v OpenAPI."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def sync_birth_fields(data: Any) -> Any:
|
|
68
|
+
"""Sjednotí ``birth_date`` (nové, ``date``) a ``birthdate`` (staré, ``datetime``) při validaci vstupu.
|
|
69
|
+
|
|
70
|
+
- zadáno ``birth_date`` → platí a ``birthdate`` se dopočítá jako půlnoc téhož dne (bez zóny),
|
|
71
|
+
- zadáno jen ``birthdate`` → ``birth_date`` se dopočítá (zóna → den v Praze), ``birthdate`` zůstane beze změny,
|
|
72
|
+
- nezadáno nic → data se nemění (kvůli ``exclude_unset``).
|
|
73
|
+
Vstup se nemění na místě.
|
|
74
|
+
"""
|
|
75
|
+
if not isinstance(data, dict):
|
|
76
|
+
return data
|
|
77
|
+
new_value = data.get('birth_date')
|
|
78
|
+
old_value = data.get('birthdate')
|
|
79
|
+
if new_value not in (None, ''):
|
|
80
|
+
day = to_calendar_date(new_value)
|
|
81
|
+
data = dict(data)
|
|
82
|
+
data['birth_date'] = day
|
|
83
|
+
data['birthdate'] = to_midnight(day)
|
|
84
|
+
elif old_value not in (None, ''):
|
|
85
|
+
data = dict(data)
|
|
86
|
+
data['birth_date'] = to_calendar_date(old_value)
|
|
87
|
+
return data
|
|
@@ -3,10 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Any, List, Optional
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field, SecretStr, StrictBool, AliasChoices
|
|
6
|
+
from pydantic import BaseModel, Field, SecretStr, StrictBool, AliasChoices, model_validator
|
|
7
7
|
from sysnet_pyutils.models.general import LocationType, ListTypeBase, MetadataEssentialType
|
|
8
8
|
|
|
9
9
|
from persons_model.common import ContactType, RegistryType
|
|
10
|
+
from persons_model.dates import PureDate, sync_birth_fields
|
|
10
11
|
from persons_model.tag import TagType
|
|
11
12
|
|
|
12
13
|
|
|
@@ -23,11 +24,21 @@ class IndividualBaseType(BaseModel):
|
|
|
23
24
|
address: Optional[List[Optional[LocationType]]] = Field(default=None, description='Seznam adres')
|
|
24
25
|
address_default: Optional[int] = Field(default=0, description="Primární adresní údaje")
|
|
25
26
|
address_printable: Optional[str] = Field(default=None, description="Adresa pro tisk")
|
|
26
|
-
birthdate: Optional[datetime] = Field(
|
|
27
|
+
birthdate: Optional[datetime] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Datum narození jako okamžik (zastaralé, ponecháno kvůli zpětné kompatibilitě). "
|
|
30
|
+
"Použijte birth_date.", json_schema_extra={'deprecated': True})
|
|
31
|
+
birth_date: Optional[PureDate] = Field(
|
|
32
|
+
default=None, description="Datum narození jako čisté datum (YYYY-MM-DD, bez času a zóny)")
|
|
27
33
|
gdpr: Optional[StrictBool] = Field(default=True, description="Udělen souhlas se zpracováním dat GDPR")
|
|
28
34
|
note: Optional[str] = Field(default=None, description='Libovolná poznámka')
|
|
29
35
|
agenda: Optional[dict[str, dict[str, Any]]] = Field(default=None, description='Agendově specifická data. Klíč = kód agendy (hnvo, ippc, cites). Hodnota = libovolný objekt parsovaný konzumentem.')
|
|
30
36
|
|
|
37
|
+
@model_validator(mode='before')
|
|
38
|
+
@classmethod
|
|
39
|
+
def _sync_birth_fields(cls, data):
|
|
40
|
+
return sync_birth_fields(data)
|
|
41
|
+
|
|
31
42
|
|
|
32
43
|
class IndividualEntryType(BaseModel):
|
|
33
44
|
"""
|
|
@@ -3,11 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field, StrictBool, AliasChoices
|
|
6
|
+
from pydantic import BaseModel, Field, StrictBool, AliasChoices, model_validator
|
|
7
7
|
from sysnet_pyutils.models.general import LocationType, PersonTypeEnum, ListTypeBase, MetadataEssentialType
|
|
8
8
|
|
|
9
|
-
from persons_model.common import RedundantType, DepartmentType, ContactType, IssuingType, RegistryType, MemberType, ScopeType
|
|
9
|
+
from persons_model.common import RedundantType, DepartmentType, ContactType, ContactEntryType, IssuingType, RegistryType, MemberType, ScopeType
|
|
10
10
|
from persons_model.legal_form import LegalFormEnum, LegalFormType, make_legal_form, person_type_from_legal_form
|
|
11
|
+
from persons_model.dates import PureDate, sync_birth_fields
|
|
11
12
|
from persons_model.tag import TagType
|
|
12
13
|
|
|
13
14
|
|
|
@@ -43,6 +44,17 @@ class PersonEntityEntryType(BaseModel):
|
|
|
43
44
|
registry: Optional[RegistryType] = Field(default=None, description="Vazba na referenční registr (CRŽP uuid)")
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
class PersonEntityExtEntryType(PersonEntityEntryType):
|
|
48
|
+
"""
|
|
49
|
+
Položka rozšířeného výpisu subjektů (``GET /person?extended=true``, Redmine #10281).
|
|
50
|
+
|
|
51
|
+
Ke standardní položce přidává kontakty a zástupce, aby je klient (katalog úřadů) nemusel načítat
|
|
52
|
+
z detailu každého subjektu zvlášť. Datum narození se ve výpisu nevrací.
|
|
53
|
+
"""
|
|
54
|
+
contacts: Optional[List[Optional[ContactEntryType]]] = Field(default=None, description="Kontakty subjektu (e-mail, telefon, datová schránka, web)")
|
|
55
|
+
representative: Optional[ContactEntryType] = Field(default=None, description="Kontaktní osoba / zástupce subjektu")
|
|
56
|
+
|
|
57
|
+
|
|
46
58
|
class PersonEntityListType(ListTypeBase):
|
|
47
59
|
"""
|
|
48
60
|
Seznam vrácených subjektů
|
|
@@ -51,13 +63,26 @@ class PersonEntityListType(ListTypeBase):
|
|
|
51
63
|
entries: Optional[List[Optional[PersonEntityEntryType]]] = Field(default=None, description='Položky seznamu')
|
|
52
64
|
|
|
53
65
|
|
|
66
|
+
class PersonEntityExtListType(ListTypeBase):
|
|
67
|
+
"""
|
|
68
|
+
Seznam subjektů s kontakty (``GET /person?extended=true``)
|
|
69
|
+
"""
|
|
70
|
+
hits: Optional[int] = Field(default=None, description="celkový počet vybraných položek")
|
|
71
|
+
entries: Optional[List[Optional[PersonEntityExtEntryType]]] = Field(default=None, description='Položky seznamu')
|
|
72
|
+
|
|
73
|
+
|
|
54
74
|
class PersonEntityBaseType(BaseModel):
|
|
55
75
|
"""
|
|
56
76
|
Osoba, subjekt. Fyzická nebo právnická osoba.
|
|
57
77
|
""" # noqa: E501
|
|
58
78
|
code: Optional[str] = Field(default=None, description="Kód subjektu")
|
|
59
79
|
ico: Optional[str] = Field(default=None, description="Jiný identifikátor (IČO)")
|
|
60
|
-
birthdate: Optional[datetime] = Field(
|
|
80
|
+
birthdate: Optional[datetime] = Field(
|
|
81
|
+
default=None,
|
|
82
|
+
description="Datum narození jako okamžik (zastaralé, ponecháno kvůli zpětné kompatibilitě). "
|
|
83
|
+
"Použijte birth_date.", json_schema_extra={'deprecated': True})
|
|
84
|
+
birth_date: Optional[PureDate] = Field(
|
|
85
|
+
default=None, description="Datum narození jako čisté datum (YYYY-MM-DD, bez času a zóny)")
|
|
61
86
|
person_type: Optional[PersonTypeEnum] = Field(default=None, description='Typ osoby (zdroj CRŽP)')
|
|
62
87
|
legal_form: Optional[LegalFormType] = Field(default=None, description='Právní forma osoby (číselník EGON RPP); pole code = LegalFormEnum, pole name = český název')
|
|
63
88
|
name: Optional[str] = Field(default=None, description="Název nebo jméno osoby")
|
|
@@ -74,6 +99,11 @@ class PersonEntityBaseType(BaseModel):
|
|
|
74
99
|
scope: Optional[ScopeType] = Field(default=None, description="Rozsah působnosti subjektu")
|
|
75
100
|
agenda: Optional[dict[str, dict[str, Any]]] = Field(default=None, description="Agendově specifická data. Klíč = kód agendy (hnvo, ippc, cites). Hodnota = libovolný objekt parsovaný konzumentem.")
|
|
76
101
|
|
|
102
|
+
@model_validator(mode='before')
|
|
103
|
+
@classmethod
|
|
104
|
+
def _sync_birth_fields(cls, data):
|
|
105
|
+
return sync_birth_fields(data)
|
|
106
|
+
|
|
77
107
|
def set_legal_form(self, code: Union[LegalFormEnum, str]) -> "PersonEntityBaseType":
|
|
78
108
|
"""Nastaví atribut legal_form podle kódu právní formy.
|
|
79
109
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sysnet-persons-model"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Data Developer", email="info@sysnet.cz" },
|
|
10
10
|
]
|
|
@@ -28,6 +28,7 @@ dependencies = {file = ["requirements.txt"]}
|
|
|
28
28
|
[project.optional-dependencies]
|
|
29
29
|
dev = [
|
|
30
30
|
"pytest>=9.0",
|
|
31
|
+
"pytest-asyncio>=0.26",
|
|
31
32
|
"build>=1.0",
|
|
32
33
|
"twine>=5.0",
|
|
33
34
|
]
|
{sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-persons-model
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: SYSNET Persons Data Model
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
License-Expression: LicenseRef-SYSNET-FreeForHomeUse
|
|
@@ -18,12 +18,15 @@ Requires-Dist: sysnet-pyutils>=1.7.0
|
|
|
18
18
|
Requires-Dist: pydantic[email]>=2.11.4
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pytest>=9.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.26; extra == "dev"
|
|
21
22
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
22
23
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# sysnet-persons-model
|
|
26
27
|
|
|
28
|
+

|
|
29
|
+
|
|
27
30
|
Data model pro entity a osoby v ekosystému SYSNET. Postaveno na Pydantic v2.
|
|
28
31
|
|
|
29
32
|
## Instalace
|
|
@@ -39,6 +42,7 @@ pip install sysnet-persons-model
|
|
|
39
42
|
- **Opaque extension slot** (`agenda`) pro agendově specifická data.
|
|
40
43
|
|
|
41
44
|
## Verze
|
|
45
|
+
- **1.4.2**: Oprava validace `ScopeType` — rozsah `GLOBAL` s neprázdným seznamem jurisdikcí nyní vyvolá Pydantic `ValidationError` se zprávou „Globální rozsah nesmí definovat jurisdikce.“ místo jejich tichého ignorování. Sjednocení souboru `VERSION` na `1.4.2` a přidání `pytest-asyncio>=0.26` do vývojových závislostí. Viz [CHANGELOG.md](CHANGELOG.md).
|
|
42
46
|
- **1.3.0**: Přidání atributu `agenda` (opaque extension slot) do `IndividualBaseType` a `PersonEntityBaseType`. Viz [AGENDA_GUIDE.md](docs/AGENDA_GUIDE.md).
|
|
43
47
|
- **1.2.8**: Interní verze, přidání atributu `agenda` (špatná cesta k docs).
|
|
44
48
|
- **1.2.7**: Přidání aliasu `metadata` pro pole `document` (Redmine #9850).
|
{sysnet_persons_model-1.4.2 → sysnet_persons_model-1.6.0}/sysnet_persons_model.egg-info/SOURCES.txt
RENAMED
|
@@ -7,6 +7,7 @@ persons_model/common.py
|
|
|
7
7
|
persons_model/config.py
|
|
8
8
|
persons_model/context.py
|
|
9
9
|
persons_model/country.py
|
|
10
|
+
persons_model/dates.py
|
|
10
11
|
persons_model/individual.py
|
|
11
12
|
persons_model/legal_form.py
|
|
12
13
|
persons_model/person_entity.py
|
|
@@ -17,6 +18,7 @@ sysnet_persons_model.egg-info/SOURCES.txt
|
|
|
17
18
|
sysnet_persons_model.egg-info/dependency_links.txt
|
|
18
19
|
sysnet_persons_model.egg-info/requires.txt
|
|
19
20
|
sysnet_persons_model.egg-info/top_level.txt
|
|
21
|
+
tests/test_birth_date.py
|
|
20
22
|
tests/test_common.py
|
|
21
23
|
tests/test_config.py
|
|
22
24
|
tests/test_context.py
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testy pole ``birth_date`` (čisté datum) vedle ``birthdate`` (datetime) — Redmine #10334, cesta C.
|
|
3
|
+
|
|
4
|
+
Pravidla:
|
|
5
|
+
- ``birth_date`` je kalendářní datum (``datetime.date``), v JSON ``YYYY-MM-DD``, v OpenAPI ``format: date``.
|
|
6
|
+
- Hodnota se zónou (``Z``, ``+00:00``) se převádí na den v ``Europe/Prague``; hodnota bez zóny se bere tak, jak je.
|
|
7
|
+
- ``birthdate`` zůstává kvůli zpětné kompatibilitě; při vstupu jen s ním se ``birth_date`` dopočítá,
|
|
8
|
+
při vstupu jen s ``birth_date`` se ``birthdate`` dopočítá jako půlnoc téhož dne (bez zóny, jako dosud).
|
|
9
|
+
- Pokud přijdou obě pole, platí ``birth_date`` (nové pole má přednost).
|
|
10
|
+
"""
|
|
11
|
+
from datetime import date, datetime
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from persons_model.common import ContactType
|
|
16
|
+
from persons_model.dates import to_calendar_date
|
|
17
|
+
from persons_model.individual import IndividualBaseType, IndividualType
|
|
18
|
+
from persons_model.person_entity import PersonEntityBaseType, PersonEntityType
|
|
19
|
+
|
|
20
|
+
TYPES = [ContactType, IndividualBaseType, IndividualType, PersonEntityBaseType, PersonEntityType]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestToCalendarDate:
|
|
24
|
+
@pytest.mark.parametrize("raw, expected", [
|
|
25
|
+
("1972-08-26", date(1972, 8, 26)),
|
|
26
|
+
("1972-08-26T00:00:00", date(1972, 8, 26)),
|
|
27
|
+
("1972-08-26T12:00:00", date(1972, 8, 26)),
|
|
28
|
+
("1972-08-26 12:00:00", date(1972, 8, 26)),
|
|
29
|
+
# hodnoty se zónou: den v pražském čase. V roce 1972 platilo SEČ (UTC+1) i v létě,
|
|
30
|
+
# letní čas byl zaveden až 1979 — zoneinfo to ví.
|
|
31
|
+
("1972-08-26T00:00:00Z", date(1972, 8, 26)), # 01:00 SEČ
|
|
32
|
+
("1972-08-25T22:00:00Z", date(1972, 8, 25)), # 23:00 SEČ 25. 8.
|
|
33
|
+
("1972-08-25T23:00:00Z", date(1972, 8, 26)), # 00:00 SEČ 26. 8.
|
|
34
|
+
("1972-12-31T23:00:00Z", date(1973, 1, 1)), # 00:00 SEČ
|
|
35
|
+
("1972-12-31T22:00:00Z", date(1972, 12, 31)),
|
|
36
|
+
("1972-08-26T00:00:00+01:00", date(1972, 8, 26)), # zóna odpovídá Praze
|
|
37
|
+
(datetime(1972, 8, 26, 0, 0), date(1972, 8, 26)),
|
|
38
|
+
(datetime(1972, 8, 26, 12, 0), date(1972, 8, 26)),
|
|
39
|
+
(date(1972, 8, 26), date(1972, 8, 26)),
|
|
40
|
+
(None, None),
|
|
41
|
+
])
|
|
42
|
+
def test_prevod(self, raw, expected):
|
|
43
|
+
assert to_calendar_date(raw) == expected
|
|
44
|
+
|
|
45
|
+
def test_letni_cas_po_roce_1979(self):
|
|
46
|
+
# 1990-07-14T22:00Z = 00:00 SELČ 15. 7. (u dat z portálu typický případ „22:00Z“)
|
|
47
|
+
assert to_calendar_date("1990-07-14T22:00:00Z") == date(1990, 7, 15)
|
|
48
|
+
# zimní 23:00Z = 00:00 SEČ
|
|
49
|
+
assert to_calendar_date("1990-01-14T23:00:00Z") == date(1990, 1, 15)
|
|
50
|
+
# hranice letního času po roce 1979: 21:00Z je ještě 23:00 SELČ téhož dne
|
|
51
|
+
assert to_calendar_date("1990-07-14T21:00:00Z") == date(1990, 7, 14)
|
|
52
|
+
|
|
53
|
+
def test_krajni_roky_nepadaji(self):
|
|
54
|
+
assert to_calendar_date("0172-08-26T00:00:00Z").year == 172
|
|
55
|
+
# okraje rozsahu datetime: posun o pražskou zónu by přetekl, vrací se den z hodnoty
|
|
56
|
+
assert to_calendar_date("9999-12-31T23:00:00Z") == date(9999, 12, 31)
|
|
57
|
+
assert to_calendar_date("0001-01-01T00:00:00Z") == date(1, 1, 1)
|
|
58
|
+
# v datech CITES jsou i roky jako 6666 — běžný převod
|
|
59
|
+
assert to_calendar_date("6666-08-26T22:00:00Z") == date(6666, 8, 27)
|
|
60
|
+
|
|
61
|
+
def test_neplatna_hodnota(self):
|
|
62
|
+
with pytest.raises(ValueError):
|
|
63
|
+
to_calendar_date("není datum")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("cls", TYPES)
|
|
67
|
+
class TestBirthDatePole:
|
|
68
|
+
def test_vychozi_hodnoty(self, cls):
|
|
69
|
+
o = cls()
|
|
70
|
+
assert o.birth_date is None
|
|
71
|
+
assert o.birthdate is None
|
|
72
|
+
|
|
73
|
+
def test_jen_birth_date_dopocita_birthdate(self, cls):
|
|
74
|
+
o = cls(birth_date="1972-08-26")
|
|
75
|
+
assert o.birth_date == date(1972, 8, 26)
|
|
76
|
+
assert o.birthdate == datetime(1972, 8, 26, 0, 0)
|
|
77
|
+
assert o.birthdate.tzinfo is None
|
|
78
|
+
|
|
79
|
+
def test_jen_birthdate_dopocita_birth_date(self, cls):
|
|
80
|
+
o = cls(birthdate="1972-08-26T12:00:00")
|
|
81
|
+
assert o.birth_date == date(1972, 8, 26)
|
|
82
|
+
|
|
83
|
+
def test_birthdate_se_zonou_je_den_v_praze(self, cls):
|
|
84
|
+
o = cls(birthdate="1990-07-14T22:00:00Z")
|
|
85
|
+
assert o.birth_date == date(1990, 7, 15)
|
|
86
|
+
|
|
87
|
+
def test_obe_pole_birth_date_ma_prednost(self, cls):
|
|
88
|
+
o = cls(birth_date="1972-08-26", birthdate="1972-08-25T22:00:00Z")
|
|
89
|
+
assert o.birth_date == date(1972, 8, 26)
|
|
90
|
+
assert o.birthdate == datetime(1972, 8, 26, 0, 0)
|
|
91
|
+
|
|
92
|
+
def test_birth_date_prijima_i_datetime_tvary(self, cls):
|
|
93
|
+
assert cls(birth_date="1972-08-26T12:00:00").birth_date == date(1972, 8, 26)
|
|
94
|
+
assert cls(birth_date="1990-07-14T22:00:00Z").birth_date == date(1990, 7, 15)
|
|
95
|
+
|
|
96
|
+
def test_json_je_cisty_datum(self, cls):
|
|
97
|
+
o = cls(birth_date="1972-08-26")
|
|
98
|
+
import json
|
|
99
|
+
dumped = json.loads(o.model_dump_json())
|
|
100
|
+
assert dumped["birth_date"] == "1972-08-26"
|
|
101
|
+
assert dumped["birthdate"] == "1972-08-26T00:00:00"
|
|
102
|
+
|
|
103
|
+
def test_openapi_schema_format_date(self, cls):
|
|
104
|
+
prop = cls.model_json_schema()["properties"]["birth_date"]
|
|
105
|
+
formats = [p.get("format") for p in prop.get("anyOf", [prop])]
|
|
106
|
+
assert "date" in formats
|
|
107
|
+
assert "date-time" not in formats
|
|
108
|
+
|
|
109
|
+
def test_exclude_unset_nezanese_nepouzite_pole(self, cls):
|
|
110
|
+
# PUT s částečným tělem: nepoužité pole se nesmí objevit jako None v exclude_unset dumpu
|
|
111
|
+
o = cls(name="x") if "name" in cls.model_fields else cls()
|
|
112
|
+
assert "birth_date" not in o.model_dump(exclude_unset=True)
|
|
113
|
+
|
|
114
|
+
def test_neplatne_datum_je_chyba(self, cls):
|
|
115
|
+
with pytest.raises(ValueError):
|
|
116
|
+
cls(birth_date="32.13.1972")
|
|
117
|
+
|
|
118
|
+
def test_roundtrip_dump_validate(self, cls):
|
|
119
|
+
o = cls(birth_date="1972-08-26")
|
|
120
|
+
again = cls.model_validate(o.model_dump())
|
|
121
|
+
assert again.birth_date == date(1972, 8, 26)
|
|
122
|
+
again2 = cls.model_validate_json(o.model_dump_json())
|
|
123
|
+
assert again2.birth_date == date(1972, 8, 26)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_vnorene_kontakty_maji_birth_date():
|
|
127
|
+
p = PersonEntityType(
|
|
128
|
+
representative={"birthdate": "1990-07-14T22:00:00Z"},
|
|
129
|
+
contacts=[{"birth_date": "1980-01-02"}],
|
|
130
|
+
)
|
|
131
|
+
assert p.representative.birth_date == date(1990, 7, 15)
|
|
132
|
+
assert p.contacts[0].birthdate == datetime(1980, 1, 2, 0, 0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_stare_ulozene_hodnoty_z_mongo_se_nactou():
|
|
136
|
+
"""Mongo vrací naivní datetime (UTC půlnoc) — načtení nesmí selhat a den zůstane."""
|
|
137
|
+
o = IndividualType(birthdate=datetime(1972, 8, 26, 0, 0))
|
|
138
|
+
assert o.birth_date == date(1972, 8, 26)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.parametrize("cls", TYPES)
|
|
142
|
+
def test_cteni_birthdate_nevyvolava_varovani(cls):
|
|
143
|
+
"""Staré pole je v OpenAPI označeno jako deprecated, ale čtení atributu nesmí hlásit DeprecationWarning."""
|
|
144
|
+
import warnings
|
|
145
|
+
o = cls(birthdate="1972-08-26")
|
|
146
|
+
with warnings.catch_warnings():
|
|
147
|
+
warnings.simplefilter("error")
|
|
148
|
+
assert o.birthdate == datetime(1972, 8, 26, 0, 0)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.parametrize("cls", TYPES)
|
|
152
|
+
def test_openapi_birthdate_je_deprecated(cls):
|
|
153
|
+
schema = cls.model_json_schema()["properties"]
|
|
154
|
+
assert schema["birthdate"].get("deprecated") is True
|
|
155
|
+
assert not schema["birth_date"].get("deprecated")
|
|
@@ -6,6 +6,8 @@ from sysnet_pyutils.models.general import LocationType, PersonTypeEnum
|
|
|
6
6
|
from persons_model.person_entity import (
|
|
7
7
|
PersonEntityLinkType,
|
|
8
8
|
PersonEntityEntryType,
|
|
9
|
+
PersonEntityExtEntryType,
|
|
10
|
+
PersonEntityExtListType,
|
|
9
11
|
PersonEntityListType,
|
|
10
12
|
PersonEntityBaseType,
|
|
11
13
|
PersonEntityType
|
|
@@ -40,6 +42,49 @@ class TestPersonEntityEntryType:
|
|
|
40
42
|
assert p.is_admin == True
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
class TestPersonEntityExtEntryType:
|
|
46
|
+
"""#10281 — rozšířený výpis: kontakty + zástupce, bez data narození."""
|
|
47
|
+
|
|
48
|
+
def test_je_rozsirenim_standardni_polozky(self):
|
|
49
|
+
assert set(PersonEntityEntryType.model_fields) < set(PersonEntityExtEntryType.model_fields)
|
|
50
|
+
assert set(PersonEntityExtEntryType.model_fields) - set(PersonEntityEntryType.model_fields) == {"contacts", "representative"}
|
|
51
|
+
|
|
52
|
+
def test_prazdny(self):
|
|
53
|
+
p = PersonEntityExtEntryType()
|
|
54
|
+
assert p.contacts is None and p.representative is None
|
|
55
|
+
|
|
56
|
+
def test_kontakty_a_zastupce(self):
|
|
57
|
+
raw = {"identifier": "u1", "name": "Úřad", "contacts": [{"email": [{"name": None, "email": "a@b.cz"}], "isds": "bbb666",
|
|
58
|
+
"web": "http://x.cz", "phone": [{"number": "606111222"}], "phoneDefault": 0}],
|
|
59
|
+
"representative": {"full_name": "Přemysl Ďáblík", "birthdate": "1970-01-01T00:00:00", "email": [{"email": "d@h.cz"}]}}
|
|
60
|
+
p = PersonEntityExtEntryType(**raw)
|
|
61
|
+
assert p.contacts[0].isds == "bbb666" and p.contacts[0].email[0].email == "a@b.cz"
|
|
62
|
+
assert p.contacts[0].phone[0].number == "606111222"
|
|
63
|
+
assert p.representative.full_name == "Přemysl Ďáblík"
|
|
64
|
+
|
|
65
|
+
def test_datum_narozeni_se_do_vypisu_nedostane(self):
|
|
66
|
+
p = PersonEntityExtEntryType(identifier="u1", representative={"full_name": "X", "birthdate": "1970-01-01T00:00:00"})
|
|
67
|
+
assert not hasattr(p.representative, "birthdate")
|
|
68
|
+
assert "birthdate" not in p.model_dump()["representative"]
|
|
69
|
+
assert "birthdate" not in p.model_dump(by_alias=True)["representative"]
|
|
70
|
+
|
|
71
|
+
def test_alias_phoneDefault_ve_vystupu(self):
|
|
72
|
+
p = PersonEntityExtEntryType(identifier="u1", contacts=[{"phoneDefault": 1}])
|
|
73
|
+
assert p.model_dump(by_alias=True)["contacts"][0]["phoneDefault"] == 1
|
|
74
|
+
|
|
75
|
+
def test_plny_model_se_do_vypisu_prevede(self):
|
|
76
|
+
"""DbPerson.model_dump() obsahuje contacts s birthdate a další pole — Pydantic je tiše zahodí."""
|
|
77
|
+
full = PersonEntityType(identifier="u1", name="Úřad", contacts=[{"isds": "x", "birthdate": "1970-01-01T00:00:00"}],
|
|
78
|
+
representative={"full_name": "Z", "birthdate": "1970-01-01T00:00:00"}, note="interní")
|
|
79
|
+
e = PersonEntityExtEntryType(**full.model_dump())
|
|
80
|
+
assert e.contacts[0].isds == "x" and e.representative.full_name == "Z"
|
|
81
|
+
assert not hasattr(e, "note")
|
|
82
|
+
|
|
83
|
+
def test_list(self):
|
|
84
|
+
l = PersonEntityExtListType(hits=1, entries=[PersonEntityExtEntryType(identifier="u1")])
|
|
85
|
+
assert l.hits == 1 and l.entries[0].identifier == "u1"
|
|
86
|
+
|
|
87
|
+
|
|
43
88
|
class TestPersonEntityType:
|
|
44
89
|
def test_prazdny(self):
|
|
45
90
|
p = PersonEntityType()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|