Skip to content

Commit d3ccf64

Browse files
authored
🎨Rework imports - avoid circular import errors (#790)
* Rework imports * Use ForwardReferences for BO4E classes * Rebuild models in init.py * Import `enum.Typ` always to support default-value * Don't rebuild on docstring decorator * Support inheritance * Support default value `Landescode` * 🩹some fixes * Add missing import `Messwerterfassung` to `__init__.py` * Check if it is a type * isort . * 📄add comment * Remove unused imports * Don't cover type checking code * Bump pre-commit black version
1 parent 7e1c2cf commit d3ccf64

84 files changed

Lines changed: 958 additions & 739 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎pyproject.toml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,9 @@ init_forbid_extra = true
8888
init_typed = true
8989
warn_required_dynamic_aliases = true
9090
warn_untyped_fields = true
91+
92+
[tool.coverage.report]
93+
exclude_also = [
94+
# This covers both typing.TYPE_CHECKING and plain TYPE_CHECKING, with any amount of whitespace
95+
"if\\s+(typing\\.)?TYPE_CHECKING:"
96+
]

‎src/bo4e/__init__.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
"Messart",
140140
"Messgroesse",
141141
"Messpreistyp",
142+
"Messwerterfassung",
142143
"Messwertstatus",
143144
"Messwertstatuszusatz",
144145
"Netzebene",
@@ -187,6 +188,8 @@
187188
"__gh_version__",
188189
]
189190

191+
from pydantic import BaseModel as _PydanticBaseModel
192+
190193
# Import BOs
191194
from .bo.angebot import Angebot
192195
from .bo.ausschreibung import Ausschreibung
@@ -322,6 +325,7 @@
322325
from .enum.messart import Messart
323326
from .enum.messgroesse import Messgroesse
324327
from .enum.messpreistyp import Messpreistyp
328+
from .enum.messwerterfassung import Messwerterfassung
325329
from .enum.messwertstatus import Messwertstatus
326330
from .enum.messwertstatuszusatz import Messwertstatuszusatz
327331
from .enum.netzebene import Netzebene
@@ -367,3 +371,10 @@
367371
from .enum.zaehlertypspezifikation import ZaehlertypSpezifikation
368372
from .version import __gh_version__, __version__
369373
from .zusatzattribut import ZusatzAttribut
374+
375+
# Resolve all ForwardReferences. This design prevents circular import errors.
376+
for cls_name in __all__:
377+
cls = globals().get(cls_name, None)
378+
if cls is None or not isinstance(cls, type) or not issubclass(cls, _PydanticBaseModel):
379+
continue
380+
cls.model_rebuild(force=True)

‎src/bo4e/bo/angebot.py‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44

55
# pylint: disable=too-few-public-methods, too-many-instance-attributes
66
# pylint: disable=no-name-in-module
7-
from typing import Annotated, Optional
7+
from typing import TYPE_CHECKING, Annotated, Optional
88

99
import pydantic
1010
from pydantic import Field
1111

12-
from ..com.angebotsvariante import Angebotsvariante
13-
from ..enum.sparte import Sparte
1412
from ..enum.typ import Typ
1513
from ..utils import postprocess_docstring
1614
from .geschaeftsobjekt import Geschaeftsobjekt
17-
from .geschaeftspartner import Geschaeftspartner
18-
from .person import Person
15+
16+
if TYPE_CHECKING:
17+
from ..com.angebotsvariante import Angebotsvariante
18+
from ..enum.sparte import Sparte
19+
from .geschaeftspartner import Geschaeftspartner
20+
from .person import Person
1921

2022

2123
@postprocess_docstring
@@ -36,19 +38,19 @@ class Angebot(Geschaeftsobjekt):
3638
3739
"""
3840

39-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.ANGEBOT
41+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.ANGEBOT
4042
#: Eindeutige Nummer des Angebotes
4143
angebotsnummer: Optional[str] = None
4244
#: Erstellungsdatum des Angebots
4345
angebotsdatum: Optional[pydantic.AwareDatetime] = None
4446
#: Sparte, fĂźr die das Angebot abgegeben wird (Strom/Gas)
45-
sparte: Optional[Sparte] = None
47+
sparte: Optional["Sparte"] = None
4648
#: Ersteller des Angebots
47-
angebotsgeber: Optional[Geschaeftspartner] = None
49+
angebotsgeber: Optional["Geschaeftspartner"] = None
4850
#: Empfänger des Angebots
49-
angebotsnehmer: Optional[Geschaeftspartner] = None
51+
angebotsnehmer: Optional["Geschaeftspartner"] = None
5052

51-
varianten: Optional[list[Angebotsvariante]] = None
53+
varianten: Optional[list["Angebotsvariante"]] = None
5254
""" Eine oder mehrere Varianten des Angebots mit den Angebotsteilen;
5355
Ein Angebot besteht mindestens aus einer Variante."""
5456

@@ -58,6 +60,6 @@ class Angebot(Geschaeftsobjekt):
5860
#: Bis zu diesem Zeitpunkt (Tag/Uhrzeit) inklusive gilt das Angebot
5961
bindefrist: Optional[pydantic.AwareDatetime] = None
6062
#: Person, die als Angebotsnehmer das Angebot angenommen hat
61-
unterzeichner_angebotsnehmer: Optional[Person] = None
63+
unterzeichner_angebotsnehmer: Optional["Person"] = None
6264
#: Person, die als Angebotsgeber das Angebots ausgestellt hat
63-
unterzeichner_angebotsgeber: Optional[Person] = None
65+
unterzeichner_angebotsgeber: Optional["Person"] = None

‎src/bo4e/bo/ausschreibung.py‎

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44

55
# pylint: disable=too-few-public-methods, too-many-instance-attributes
66
# pylint: disable=no-name-in-module
7-
from typing import Annotated, Optional
7+
from typing import TYPE_CHECKING, Annotated, Optional
88

99
import pydantic
1010
from pydantic import Field
1111

12-
from ..com.ausschreibungslos import Ausschreibungslos
13-
from ..com.zeitraum import Zeitraum
14-
from ..enum.ausschreibungsportal import Ausschreibungsportal
15-
from ..enum.ausschreibungsstatus import Ausschreibungsstatus
16-
from ..enum.ausschreibungstyp import Ausschreibungstyp
1712
from ..enum.typ import Typ
1813
from ..utils import postprocess_docstring
1914
from .geschaeftsobjekt import Geschaeftsobjekt
20-
from .geschaeftspartner import Geschaeftspartner
15+
16+
if TYPE_CHECKING:
17+
from ..com.ausschreibungslos import Ausschreibungslos
18+
from ..com.zeitraum import Zeitraum
19+
from ..enum.ausschreibungsportal import Ausschreibungsportal
20+
from ..enum.ausschreibungsstatus import Ausschreibungsstatus
21+
from ..enum.ausschreibungstyp import Ausschreibungstyp
22+
from .geschaeftspartner import Geschaeftspartner
2123

2224

2325
@postprocess_docstring
@@ -34,36 +36,36 @@ class Ausschreibung(Geschaeftsobjekt):
3436
3537
"""
3638

37-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.AUSSCHREIBUNG
39+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.AUSSCHREIBUNG
3840
#: Vom Herausgeber der Ausschreibung vergebene eindeutige Nummer
3941
ausschreibungsnummer: Optional[str] = None
4042
#: Aufzählung fßr die Typisierung von Ausschreibungen
41-
ausschreibungstyp: Optional[Ausschreibungstyp] = None
43+
ausschreibungstyp: Optional["Ausschreibungstyp"] = None
4244
#: Bezeichnungen fĂźr die Ausschreibungsphasen
43-
ausschreibungsstatus: Optional[Ausschreibungsstatus] = None
45+
ausschreibungsstatus: Optional["Ausschreibungsstatus"] = None
4446
#: Kennzeichen, ob die Ausschreibung kostenpflichtig ist
4547
ist_kostenpflichtig: Optional[bool] = None
4648
#: Gibt den VerĂśffentlichungszeitpunkt der Ausschreibung an
4749
veroeffentlichungszeitpunkt: Optional[pydantic.AwareDatetime] = None
48-
ausschreibender: Optional[Geschaeftspartner] = None
50+
ausschreibender: Optional["Geschaeftspartner"] = None
4951
"""
5052
Mit diesem Objekt kÜnnen Geschäftspartner ßbertragen werden.
5153
Sowohl Unternehmen, als auch Privatpersonen kÜnnen Geschäftspartner sein
5254
"""
53-
abgabefrist: Optional[Zeitraum] = None
55+
abgabefrist: Optional["Zeitraum"] = None
5456
"""
5557
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
5658
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
5759
"""
58-
bindefrist: Optional[Zeitraum] = None
60+
bindefrist: Optional["Zeitraum"] = None
5961
"""
6062
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
6163
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
6264
"""
6365
#: Die einzelnen Lose, aus denen sich die Ausschreibung zusammensetzt
64-
lose: Optional[list[Ausschreibungslos]] = None
66+
lose: Optional[list["Ausschreibungslos"]] = None
6567

6668
#: Aufzählung der unterstßtzten Ausschreibungsportale
67-
ausschreibungportal: Optional[Ausschreibungsportal] = None
69+
ausschreibungportal: Optional["Ausschreibungsportal"] = None
6870
#: Internetseite, auf der die Ausschreibung verĂśffentlicht wurde (falls vorhanden)
6971
webseite: Optional[str] = None

‎src/bo4e/bo/buendelvertrag.py‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,23 @@
44

55
# pylint: disable=too-few-public-methods
66
# pylint: disable=no-name-in-module
7-
from typing import Annotated, Optional
7+
from typing import TYPE_CHECKING, Annotated, Optional
88

99
import pydantic
1010
from pydantic import Field
1111

12-
from ..com.unterschrift import Unterschrift
13-
from ..com.vertragskonditionen import Vertragskonditionen
14-
from ..enum.sparte import Sparte
1512
from ..enum.typ import Typ
16-
from ..enum.vertragsart import Vertragsart
17-
from ..enum.vertragsstatus import Vertragsstatus
1813
from ..utils import postprocess_docstring
1914
from .geschaeftsobjekt import Geschaeftsobjekt
20-
from .geschaeftspartner import Geschaeftspartner
21-
from .vertrag import Vertrag
15+
16+
if TYPE_CHECKING:
17+
from ..com.unterschrift import Unterschrift
18+
from ..com.vertragskonditionen import Vertragskonditionen
19+
from ..enum.sparte import Sparte
20+
from ..enum.vertragsart import Vertragsart
21+
from ..enum.vertragsstatus import Vertragsstatus
22+
from .geschaeftspartner import Geschaeftspartner
23+
from .vertrag import Vertrag
2224

2325

2426
@postprocess_docstring
@@ -36,35 +38,35 @@ class Buendelvertrag(Geschaeftsobjekt):
3638
3739
"""
3840

39-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.BUENDELVERTRAG
41+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.BUENDELVERTRAG
4042

4143
# pylint: disable=duplicate-code
4244
#: Eine im Verwendungskontext eindeutige Nummer fĂźr den Vertrag
4345
vertragsnummer: Optional[str] = None
4446
#: Hier ist festgelegt, um welche Art von Vertrag es sich handelt. Z.B. Netznutzungvertrag
45-
vertragsart: Optional[Vertragsart] = None
47+
vertragsart: Optional["Vertragsart"] = None
4648
#: Gibt den Status des Vertrages an
47-
vertragsstatus: Optional[Vertragsstatus] = None
49+
vertragsstatus: Optional["Vertragsstatus"] = None
4850
#: UnterscheidungsmĂśglichkeiten fĂźr die Sparte
49-
sparte: Optional[Sparte] = None
51+
sparte: Optional["Sparte"] = None
5052
#: Gibt an, wann der Vertrag beginnt (inklusiv)
5153
vertragsbeginn: Optional[pydantic.AwareDatetime] = None
5254
#: Gibt an, wann der Vertrag (voraussichtlich) endet oder beendet wurde (exklusiv)
5355
vertragsende: Optional[pydantic.AwareDatetime] = None
5456
#: Der "erstgenannte" Vertragspartner. In der Regel der Aussteller des Vertrags.
5557
#: Beispiel: "Vertrag zwischen Vertagspartner 1 ..."
56-
vertragspartner1: Optional[Geschaeftspartner] = None
58+
vertragspartner1: Optional["Geschaeftspartner"] = None
5759
#: Der "zweitgenannte" Vertragspartner. In der Regel der Empfänger des Vertrags.
5860
#: Beispiel "Vertrag zwischen Vertagspartner 1 und Vertragspartner 2"
59-
vertragspartner2: Optional[Geschaeftspartner] = None
61+
vertragspartner2: Optional["Geschaeftspartner"] = None
6062

6163
#: Die Liste mit den Einzelverträgen zu den Abnahmestellen
62-
einzelvertraege: Optional[list[Vertrag]] = None
64+
einzelvertraege: Optional[list["Vertrag"]] = None
6365
#: Festlegungen zu Laufzeiten und KĂźndigungsfristen
64-
vertragskonditionen: Optional[list[Vertragskonditionen]] = None
66+
vertragskonditionen: Optional[list["Vertragskonditionen"]] = None
6567
#: Unterzeichner des Vertragspartners1
66-
unterzeichnervp1: Optional[list[Unterschrift]] = None
68+
unterzeichnervp1: Optional[list["Unterschrift"]] = None
6769
#: Unterzeichner des Vertragspartners2
68-
unterzeichnervp2: Optional[list[Unterschrift]] = None
70+
unterzeichnervp2: Optional[list["Unterschrift"]] = None
6971
#: Beschreibung zum Vertrag
7072
beschreibung: Optional[str] = None

‎src/bo4e/bo/energiemenge.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
and corresponding marshmallow schema for de-/serialization
44
"""
55

6-
from typing import Annotated, Optional
6+
from typing import TYPE_CHECKING, Annotated, Optional
77

88
from pydantic import Field
99

10-
from ..com.verbrauch import Verbrauch
11-
from ..enum.lokationstyp import Lokationstyp
1210
from ..enum.typ import Typ
1311
from ..utils import postprocess_docstring
1412
from .geschaeftsobjekt import Geschaeftsobjekt
1513

14+
if TYPE_CHECKING:
15+
from ..com.verbrauch import Verbrauch
16+
from ..enum.lokationstyp import Lokationstyp
17+
18+
1619
# pylint: disable=too-few-public-methods
1720
# pylint: disable=no-name-in-module
1821

@@ -31,13 +34,13 @@ class Energiemenge(Geschaeftsobjekt):
3134
3235
"""
3336

34-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.ENERGIEMENGE
37+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.ENERGIEMENGE
3538
#: Eindeutige Nummer der Marktlokation bzw. der Messlokation, zu der die Energiemenge gehĂśrt
3639
lokations_id: Optional[str] = None
3740
# todo: add validator such that only mess- or marktlokations IDs are accepted + cross check with lokationstyp
3841
#: Gibt an, ob es sich um eine Markt- oder Messlokation handelt
39-
lokationstyp: Optional[Lokationstyp] = None
42+
lokationstyp: Optional["Lokationstyp"] = None
4043

4144
#: Gibt den Verbrauch in einer Zeiteinheit an
42-
energieverbrauch: Optional[list[Verbrauch]] = None
45+
energieverbrauch: Optional[list["Verbrauch"]] = None
4346
# there are no optional attributes

‎src/bo4e/bo/fremdkosten.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
Contains Fremdkosten class and corresponding marshmallow schema for de-/serialization
33
"""
44

5-
from typing import Annotated, Optional
5+
from typing import TYPE_CHECKING, Annotated, Optional
66

77
from pydantic import Field
88

9-
from ..com.betrag import Betrag
10-
from ..com.fremdkostenblock import Fremdkostenblock
11-
from ..com.zeitraum import Zeitraum
129
from ..enum.typ import Typ
1310
from ..utils import postprocess_docstring
1411
from .geschaeftsobjekt import Geschaeftsobjekt
1512

13+
if TYPE_CHECKING:
14+
from ..com.betrag import Betrag
15+
from ..com.fremdkostenblock import Fremdkostenblock
16+
from ..com.zeitraum import Zeitraum
17+
18+
1619
# pylint: disable=too-few-public-methods
1720

1821

@@ -32,10 +35,10 @@ class Fremdkosten(Geschaeftsobjekt):
3235
3336
"""
3437

35-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.FREMDKOSTEN
38+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.FREMDKOSTEN
3639
#: FĂźr diesen Zeitraum wurden die Kosten ermittelt
37-
gueltigkeit: Optional[Zeitraum] = None
40+
gueltigkeit: Optional["Zeitraum"] = None
3841
#: Die Gesamtsumme Ăźber alle KostenblĂścke und -positionen
39-
summe_kosten: Optional[Betrag] = None
42+
summe_kosten: Optional["Betrag"] = None
4043
#: In KostenblĂścken werden Kostenpositionen zusammengefasst. Beispiele: Netzkosten, Umlagen, Steuern etc
41-
kostenbloecke: Optional[list[Fremdkostenblock]] = None
44+
kostenbloecke: Optional[list["Fremdkostenblock"]] = None

‎src/bo4e/bo/geraet.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
and corresponding marshmallow schema for de-/serialization
44
"""
55

6-
from typing import Annotated, Optional
6+
from typing import TYPE_CHECKING, Annotated, Optional
77

88
from pydantic import Field
99

10-
from ..enum.geraeteklasse import Geraeteklasse
11-
from ..enum.geraetetyp import Geraetetyp
1210
from ..enum.typ import Typ
1311
from ..utils import postprocess_docstring
1412
from .geschaeftsobjekt import Geschaeftsobjekt
1513

14+
if TYPE_CHECKING:
15+
from ..enum.geraeteklasse import Geraeteklasse
16+
from ..enum.geraetetyp import Geraetetyp
17+
18+
1619
# pylint: disable=too-few-public-methods
1720

1821

@@ -30,13 +33,13 @@ class Geraet(Geschaeftsobjekt):
3033
3134
"""
3235

33-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.GERAET
36+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.GERAET
3437

3538
#: Die auf dem Gerät aufgedruckte Nummer, die vom MSB vergeben wird.
3639
geraetenummer: Optional[str] = None
3740
#: Bezeichnung des Geräts
3841
bezeichnung: Optional[str] = None
3942
#: Die ßbergreifende Klasse eines Geräts, beispielsweise Wandler
40-
geraeteklasse: Optional[Geraeteklasse] = None
43+
geraeteklasse: Optional["Geraeteklasse"] = None
4144
#: Der speziellere Typ eines Gerätes, beispielsweise Stromwandler
42-
geraetetyp: Optional[Geraetetyp] = None
45+
geraetetyp: Optional["Geraetetyp"] = None

‎src/bo4e/bo/geschaeftsobjekt.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ class Geschaeftsobjekt(BaseModel):
3636
__version__ #: Version der BO-Struktur aka "fachliche Versionierung"
3737
)
3838
# src/_bo4e_python_version.py
39-
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.GESCHAEFTSOBJEKT #: Der Typ des Geschäftsobjektes
39+
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.GESCHAEFTSOBJEKT #: Der Typ des Geschäftsobjektes
4040
# bo_typ is used as discriminator f.e. for databases or deserialization
4141

42-
zusatz_attribute: Optional[list[ZusatzAttribut]] = None
42+
zusatz_attribute: Optional[list["ZusatzAttribut"]] = None
4343
# zusatz_attribute is a list of ZusatzAttribut objects which are used to store additional information
4444

4545
# Python internal: The field is not named '_id' because leading underscores are not allowed in pydantic field names.

0 commit comments

Comments
 (0)