Skip to content

Commit e0af1af

Browse files
authored
feat: add JSON column w/ Kommunikationsrichtungen to Anwendungsfall table (fix cyclic import problem with Kommunikationseinrichtung model) (#181)
1 parent 63bbc6e commit e0af1af

File tree

6 files changed

+65
-12
lines changed

6 files changed

+65
-12
lines changed

src/fundamend/models/anwendungshandbuch.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from efoli import EdifactFormat
99

1010
from fundamend.models.base import FundamendBaseModel
11+
from fundamend.models.kommunikationsrichtung import Kommunikationsrichtung
12+
from fundamend.utils import parse_kommunikation_von
1113

1214

1315
class Code(FundamendBaseModel):
@@ -121,15 +123,6 @@ class SegmentGroup(FundamendBaseModel):
121123
elements: tuple["Segment | SegmentGroup", ...]
122124

123125

124-
class Kommunikationsrichtung(FundamendBaseModel):
125-
"""
126-
a strongly typed representation of the 'Kommunikation_von' attribute of anwendungsfall
127-
"""
128-
129-
sender: str #: e.g. "NB"
130-
empfaenger: str #: e.g. "MSB"
131-
132-
133126
class Anwendungsfall(FundamendBaseModel):
134127
"""
135128
One 'Anwendungsfall', indicated by `<AWF>` tag, corresponds to one Prüfidentifikator or type of Message
@@ -158,6 +151,15 @@ def is_outdated(self) -> bool:
158151
"""
159152
return "##alt##" in self.pruefidentifikator.lower() # table flip moment
160153

154+
@property
155+
def kommunikationsrichtungen(self) -> list[Kommunikationsrichtung] | None:
156+
"""
157+
the parsed 'kommunikation_von' attribute or None if it's unparsable (l)or outdated
158+
"""
159+
if self.is_outdated:
160+
return None
161+
return parse_kommunikation_von(self.kommunikation_von)
162+
161163

162164
class Bedingung(FundamendBaseModel):
163165
"""Ein ConditionKeyConditionText Mapping"""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""contains the Kommunikationsrichtung model"""
2+
3+
from fundamend.models.base import FundamendBaseModel
4+
5+
6+
# needs to be separate file/module to avoid circular imports
7+
class Kommunikationsrichtung(FundamendBaseModel):
8+
"""
9+
a strongly typed representation of the 'Kommunikation_von' attribute of anwendungsfall
10+
"""
11+
12+
sender: str #: e.g. "NB"
13+
empfaenger: str #: e.g. "MSB"
14+
15+
16+
__all__ = ["Kommunikationsrichtung"]

src/fundamend/sqlmodels/anwendungshandbuch.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
try:
16-
from sqlalchemy import CheckConstraint, UniqueConstraint
16+
from sqlalchemy import JSON, CheckConstraint, Column, UniqueConstraint
1717
from sqlmodel import Field, Relationship, SQLModel
1818
except ImportError as import_error:
1919
import_error.msg += "; Did you install fundamend[sqlmodels] or did you try to import from fundamend.models instead?"
@@ -341,6 +341,15 @@ class Anwendungsfall(SQLModel, table=True):
341341
pruefidentifikator: str = Field(index=True) #: e.g. '25001'
342342
beschreibung: str = Field(index=True) #: e.g. 'Berechnungsformel'
343343
kommunikation_von: str #: e.g. 'NB an MSB / LF'
344+
kommunikationsrichtungen: list[dict[str, str]] | None = Field(default=None, sa_column=Column(JSON))
345+
"""
346+
JSON column containing a list of dicts with keys 'sender' and 'empfaenger'.
347+
The columns value can be deserialized as list[Kommunikationsrichtung].
348+
The column contains no more information than the stringly typed 'kommunikation_von' column alone,
349+
but it's parsed already and hence easier to use and query.
350+
"""
351+
# we use a json column because setting up a separate table and FK relationships for this seems overkill
352+
# https://stackoverflow.com/a/70659555/10009545
344353
format: EdifactFormat = Field(index=True) #: e.g. 'UTILTS'
345354
segments: list[Segment] = Relationship(back_populates="anwendungsfall")
346355
segment_groups: list[SegmentGroup] = Relationship(back_populates="anwendungsfall")
@@ -356,6 +365,11 @@ def from_model(cls, model: PydanticAnwendungsfall, position: Optional[int] = Non
356365
pruefidentifikator=model.pruefidentifikator,
357366
beschreibung=model.beschreibung,
358367
kommunikation_von=model.kommunikation_von,
368+
kommunikationsrichtungen=(
369+
[kr.model_dump(mode="json") for kr in model.kommunikationsrichtungen]
370+
if model.kommunikationsrichtungen is not None
371+
else None
372+
),
359373
format=model.format,
360374
position=position,
361375
)
@@ -373,6 +387,7 @@ def to_model(self) -> PydanticAnwendungsfall:
373387
pruefidentifikator=self.pruefidentifikator,
374388
beschreibung=self.beschreibung,
375389
kommunikation_von=self.kommunikation_von,
390+
# the kommunikationsrichtungen are a computed property, so we reconstruct it from the kommunikation_von str
376391
format=self.format,
377392
elements=tuple(
378393
x.to_model()

src/fundamend/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import re
66
from typing import Optional
77

8-
from fundamend.models.anwendungshandbuch import Kommunikationsrichtung
8+
from fundamend.models.kommunikationsrichtung import Kommunikationsrichtung
99

1010

1111
def lstrip(prefix: str, text: str) -> str:

unittests/test_sqlmodels_anwendungshandbuch.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
import pytest
1010
from efoli import EdifactFormatVersion
11+
from pydantic import RootModel
1112
from sqlmodel import Session, SQLModel, create_engine, select
1213
from syrupy.assertion import SnapshotAssertion
1314

1415
from fundamend import AhbReader
1516
from fundamend.models.anwendungshandbuch import Anwendungshandbuch as PydanticAnwendunghandbuch
17+
from fundamend.models.kommunikationsrichtung import Kommunikationsrichtung
1618
from fundamend.sqlmodels import AhbHierarchyMaterialized
1719
from fundamend.sqlmodels import Anwendungshandbuch as SqlAnwendungshandbuch
1820
from fundamend.sqlmodels import create_ahb_view, create_db_and_populate_with_ahb_view
@@ -89,6 +91,9 @@ def test_sqlmodels_all_anwendungshandbuch(sqlite_session: Session) -> None:
8991
assert roundtrip_abb == ahb
9092

9193

94+
_Kommunikationsrichtungen = RootModel[list[Kommunikationsrichtung]]
95+
96+
9297
def test_sqlmodels_all_anwendungshandbuch_with_ahb_view(sqlite_session: Session) -> None:
9398
if not is_private_submodule_checked_out():
9499
pytest.skip("Skipping test because of missing private submodule")
@@ -97,6 +102,20 @@ def test_sqlmodels_all_anwendungshandbuch_with_ahb_view(sqlite_session: Session)
97102
for ahb_file_path in private_submodule_root.rglob("**/*AHB*.xml"):
98103
ahb = AhbReader(ahb_file_path).read()
99104
sql_ahb = SqlAnwendungshandbuch.from_model(ahb)
105+
for awf, sql_awf in zip(
106+
(x for x in ahb.anwendungsfaelle if not x.is_outdated),
107+
# this is because outdated AWF are not included in the SQL model;
108+
# see the SqlAnwendungshandbuch.from_model implementation.
109+
sorted(sql_ahb.anwendungsfaelle, key=lambda _awf: _awf.position or 0),
110+
):
111+
# this is for https://github.com/Hochfrequenz/xml-fundamend-python/issues/173
112+
if awf.kommunikationsrichtungen is not None and any(awf.kommunikationsrichtungen):
113+
sql_kommunikationsrichtungen = _Kommunikationsrichtungen.model_validate(
114+
sql_awf.kommunikationsrichtungen
115+
).root
116+
assert sql_kommunikationsrichtungen == awf.kommunikationsrichtungen
117+
else:
118+
assert sql_awf.kommunikationsrichtungen is None or not any(sql_awf.kommunikationsrichtungen)
100119
sqlite_session.add(sql_ahb)
101120
sqlite_session.commit()
102121
create_ahb_view(session=sqlite_session)

unittests/test_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import pytest
55

66
from fundamend import AhbReader
7-
from fundamend.models.anwendungshandbuch import Anwendungsfall, Kommunikationsrichtung
7+
from fundamend.models.anwendungshandbuch import Anwendungsfall
8+
from fundamend.models.kommunikationsrichtung import Kommunikationsrichtung
89
from fundamend.utils import parse_kommunikation_von
910

1011
from .conftest import is_private_submodule_checked_out

0 commit comments

Comments
 (0)