Skip to content

Commit

Permalink
🎨Rework imports - avoid circular import errors (#790)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
lord-haffi authored May 8, 2024
1 parent 7e1c2cf commit d3ccf64
Show file tree
Hide file tree
Showing 84 changed files with 958 additions and 739 deletions.
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true

[tool.coverage.report]
exclude_also = [
# This covers both typing.TYPE_CHECKING and plain TYPE_CHECKING, with any amount of whitespace
"if\\s+(typing\\.)?TYPE_CHECKING:"
]
11 changes: 11 additions & 0 deletions src/bo4e/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"Messart",
"Messgroesse",
"Messpreistyp",
"Messwerterfassung",
"Messwertstatus",
"Messwertstatuszusatz",
"Netzebene",
Expand Down Expand Up @@ -187,6 +188,8 @@
"__gh_version__",
]

from pydantic import BaseModel as _PydanticBaseModel

# Import BOs
from .bo.angebot import Angebot
from .bo.ausschreibung import Ausschreibung
Expand Down Expand Up @@ -322,6 +325,7 @@
from .enum.messart import Messart
from .enum.messgroesse import Messgroesse
from .enum.messpreistyp import Messpreistyp
from .enum.messwerterfassung import Messwerterfassung
from .enum.messwertstatus import Messwertstatus
from .enum.messwertstatuszusatz import Messwertstatuszusatz
from .enum.netzebene import Netzebene
Expand Down Expand Up @@ -367,3 +371,10 @@
from .enum.zaehlertypspezifikation import ZaehlertypSpezifikation
from .version import __gh_version__, __version__
from .zusatzattribut import ZusatzAttribut

# Resolve all ForwardReferences. This design prevents circular import errors.
for cls_name in __all__:
cls = globals().get(cls_name, None)
if cls is None or not isinstance(cls, type) or not issubclass(cls, _PydanticBaseModel):
continue
cls.model_rebuild(force=True)
26 changes: 14 additions & 12 deletions src/bo4e/bo/angebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@

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

import pydantic
from pydantic import Field

from ..com.angebotsvariante import Angebotsvariante
from ..enum.sparte import Sparte
from ..enum.typ import Typ
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt
from .geschaeftspartner import Geschaeftspartner
from .person import Person

if TYPE_CHECKING:
from ..com.angebotsvariante import Angebotsvariante
from ..enum.sparte import Sparte
from .geschaeftspartner import Geschaeftspartner
from .person import Person


@postprocess_docstring
Expand All @@ -36,19 +38,19 @@ class Angebot(Geschaeftsobjekt):
"""

typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.ANGEBOT
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.ANGEBOT
#: Eindeutige Nummer des Angebotes
angebotsnummer: Optional[str] = None
#: Erstellungsdatum des Angebots
angebotsdatum: Optional[pydantic.AwareDatetime] = None
#: Sparte, fĂźr die das Angebot abgegeben wird (Strom/Gas)
sparte: Optional[Sparte] = None
sparte: Optional["Sparte"] = None
#: Ersteller des Angebots
angebotsgeber: Optional[Geschaeftspartner] = None
angebotsgeber: Optional["Geschaeftspartner"] = None
#: Empfänger des Angebots
angebotsnehmer: Optional[Geschaeftspartner] = None
angebotsnehmer: Optional["Geschaeftspartner"] = None

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

Expand All @@ -58,6 +60,6 @@ class Angebot(Geschaeftsobjekt):
#: Bis zu diesem Zeitpunkt (Tag/Uhrzeit) inklusive gilt das Angebot
bindefrist: Optional[pydantic.AwareDatetime] = None
#: Person, die als Angebotsnehmer das Angebot angenommen hat
unterzeichner_angebotsnehmer: Optional[Person] = None
unterzeichner_angebotsnehmer: Optional["Person"] = None
#: Person, die als Angebotsgeber das Angebots ausgestellt hat
unterzeichner_angebotsgeber: Optional[Person] = None
unterzeichner_angebotsgeber: Optional["Person"] = None
32 changes: 17 additions & 15 deletions src/bo4e/bo/ausschreibung.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@

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

import pydantic
from pydantic import Field

from ..com.ausschreibungslos import Ausschreibungslos
from ..com.zeitraum import Zeitraum
from ..enum.ausschreibungsportal import Ausschreibungsportal
from ..enum.ausschreibungsstatus import Ausschreibungsstatus
from ..enum.ausschreibungstyp import Ausschreibungstyp
from ..enum.typ import Typ
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt
from .geschaeftspartner import Geschaeftspartner

if TYPE_CHECKING:
from ..com.ausschreibungslos import Ausschreibungslos
from ..com.zeitraum import Zeitraum
from ..enum.ausschreibungsportal import Ausschreibungsportal
from ..enum.ausschreibungsstatus import Ausschreibungsstatus
from ..enum.ausschreibungstyp import Ausschreibungstyp
from .geschaeftspartner import Geschaeftspartner


@postprocess_docstring
Expand All @@ -34,36 +36,36 @@ class Ausschreibung(Geschaeftsobjekt):
"""

typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.AUSSCHREIBUNG
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.AUSSCHREIBUNG
#: Vom Herausgeber der Ausschreibung vergebene eindeutige Nummer
ausschreibungsnummer: Optional[str] = None
#: Aufzählung fßr die Typisierung von Ausschreibungen
ausschreibungstyp: Optional[Ausschreibungstyp] = None
ausschreibungstyp: Optional["Ausschreibungstyp"] = None
#: Bezeichnungen fĂźr die Ausschreibungsphasen
ausschreibungsstatus: Optional[Ausschreibungsstatus] = None
ausschreibungsstatus: Optional["Ausschreibungsstatus"] = None
#: Kennzeichen, ob die Ausschreibung kostenpflichtig ist
ist_kostenpflichtig: Optional[bool] = None
#: Gibt den VerĂśffentlichungszeitpunkt der Ausschreibung an
veroeffentlichungszeitpunkt: Optional[pydantic.AwareDatetime] = None
ausschreibender: Optional[Geschaeftspartner] = None
ausschreibender: Optional["Geschaeftspartner"] = None
"""
Mit diesem Objekt kÜnnen Geschäftspartner ßbertragen werden.
Sowohl Unternehmen, als auch Privatpersonen kÜnnen Geschäftspartner sein
"""
abgabefrist: Optional[Zeitraum] = None
abgabefrist: Optional["Zeitraum"] = None
"""
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
"""
bindefrist: Optional[Zeitraum] = None
bindefrist: Optional["Zeitraum"] = None
"""
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
"""
#: Die einzelnen Lose, aus denen sich die Ausschreibung zusammensetzt
lose: Optional[list[Ausschreibungslos]] = None
lose: Optional[list["Ausschreibungslos"]] = None

#: Aufzählung der unterstßtzten Ausschreibungsportale
ausschreibungportal: Optional[Ausschreibungsportal] = None
ausschreibungportal: Optional["Ausschreibungsportal"] = None
#: Internetseite, auf der die Ausschreibung verĂśffentlicht wurde (falls vorhanden)
webseite: Optional[str] = None
38 changes: 20 additions & 18 deletions src/bo4e/bo/buendelvertrag.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@

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

import pydantic
from pydantic import Field

from ..com.unterschrift import Unterschrift
from ..com.vertragskonditionen import Vertragskonditionen
from ..enum.sparte import Sparte
from ..enum.typ import Typ
from ..enum.vertragsart import Vertragsart
from ..enum.vertragsstatus import Vertragsstatus
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt
from .geschaeftspartner import Geschaeftspartner
from .vertrag import Vertrag

if TYPE_CHECKING:
from ..com.unterschrift import Unterschrift
from ..com.vertragskonditionen import Vertragskonditionen
from ..enum.sparte import Sparte
from ..enum.vertragsart import Vertragsart
from ..enum.vertragsstatus import Vertragsstatus
from .geschaeftspartner import Geschaeftspartner
from .vertrag import Vertrag


@postprocess_docstring
Expand All @@ -36,35 +38,35 @@ class Buendelvertrag(Geschaeftsobjekt):
"""

typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.BUENDELVERTRAG
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.BUENDELVERTRAG

# pylint: disable=duplicate-code
#: Eine im Verwendungskontext eindeutige Nummer fĂźr den Vertrag
vertragsnummer: Optional[str] = None
#: Hier ist festgelegt, um welche Art von Vertrag es sich handelt. Z.B. Netznutzungvertrag
vertragsart: Optional[Vertragsart] = None
vertragsart: Optional["Vertragsart"] = None
#: Gibt den Status des Vertrages an
vertragsstatus: Optional[Vertragsstatus] = None
vertragsstatus: Optional["Vertragsstatus"] = None
#: UnterscheidungsmĂśglichkeiten fĂźr die Sparte
sparte: Optional[Sparte] = None
sparte: Optional["Sparte"] = None
#: Gibt an, wann der Vertrag beginnt (inklusiv)
vertragsbeginn: Optional[pydantic.AwareDatetime] = None
#: Gibt an, wann der Vertrag (voraussichtlich) endet oder beendet wurde (exklusiv)
vertragsende: Optional[pydantic.AwareDatetime] = None
#: Der "erstgenannte" Vertragspartner. In der Regel der Aussteller des Vertrags.
#: Beispiel: "Vertrag zwischen Vertagspartner 1 ..."
vertragspartner1: Optional[Geschaeftspartner] = None
vertragspartner1: Optional["Geschaeftspartner"] = None
#: Der "zweitgenannte" Vertragspartner. In der Regel der Empfänger des Vertrags.
#: Beispiel "Vertrag zwischen Vertagspartner 1 und Vertragspartner 2"
vertragspartner2: Optional[Geschaeftspartner] = None
vertragspartner2: Optional["Geschaeftspartner"] = None

#: Die Liste mit den Einzelverträgen zu den Abnahmestellen
einzelvertraege: Optional[list[Vertrag]] = None
einzelvertraege: Optional[list["Vertrag"]] = None
#: Festlegungen zu Laufzeiten und KĂźndigungsfristen
vertragskonditionen: Optional[list[Vertragskonditionen]] = None
vertragskonditionen: Optional[list["Vertragskonditionen"]] = None
#: Unterzeichner des Vertragspartners1
unterzeichnervp1: Optional[list[Unterschrift]] = None
unterzeichnervp1: Optional[list["Unterschrift"]] = None
#: Unterzeichner des Vertragspartners2
unterzeichnervp2: Optional[list[Unterschrift]] = None
unterzeichnervp2: Optional[list["Unterschrift"]] = None
#: Beschreibung zum Vertrag
beschreibung: Optional[str] = None
15 changes: 9 additions & 6 deletions src/bo4e/bo/energiemenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
and corresponding marshmallow schema for de-/serialization
"""

from typing import Annotated, Optional
from typing import TYPE_CHECKING, Annotated, Optional

from pydantic import Field

from ..com.verbrauch import Verbrauch
from ..enum.lokationstyp import Lokationstyp
from ..enum.typ import Typ
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt

if TYPE_CHECKING:
from ..com.verbrauch import Verbrauch
from ..enum.lokationstyp import Lokationstyp


# pylint: disable=too-few-public-methods
# pylint: disable=no-name-in-module

Expand All @@ -31,13 +34,13 @@ class Energiemenge(Geschaeftsobjekt):
"""

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

#: Gibt den Verbrauch in einer Zeiteinheit an
energieverbrauch: Optional[list[Verbrauch]] = None
energieverbrauch: Optional[list["Verbrauch"]] = None
# there are no optional attributes
19 changes: 11 additions & 8 deletions src/bo4e/bo/fremdkosten.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
Contains Fremdkosten class and corresponding marshmallow schema for de-/serialization
"""

from typing import Annotated, Optional
from typing import TYPE_CHECKING, Annotated, Optional

from pydantic import Field

from ..com.betrag import Betrag
from ..com.fremdkostenblock import Fremdkostenblock
from ..com.zeitraum import Zeitraum
from ..enum.typ import Typ
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt

if TYPE_CHECKING:
from ..com.betrag import Betrag
from ..com.fremdkostenblock import Fremdkostenblock
from ..com.zeitraum import Zeitraum


# pylint: disable=too-few-public-methods


Expand All @@ -32,10 +35,10 @@ class Fremdkosten(Geschaeftsobjekt):
"""

typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.FREMDKOSTEN
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.FREMDKOSTEN
#: FĂźr diesen Zeitraum wurden die Kosten ermittelt
gueltigkeit: Optional[Zeitraum] = None
gueltigkeit: Optional["Zeitraum"] = None
#: Die Gesamtsumme Ăźber alle KostenblĂścke und -positionen
summe_kosten: Optional[Betrag] = None
summe_kosten: Optional["Betrag"] = None
#: In KostenblĂścken werden Kostenpositionen zusammengefasst. Beispiele: Netzkosten, Umlagen, Steuern etc
kostenbloecke: Optional[list[Fremdkostenblock]] = None
kostenbloecke: Optional[list["Fremdkostenblock"]] = None
15 changes: 9 additions & 6 deletions src/bo4e/bo/geraet.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
and corresponding marshmallow schema for de-/serialization
"""

from typing import Annotated, Optional
from typing import TYPE_CHECKING, Annotated, Optional

from pydantic import Field

from ..enum.geraeteklasse import Geraeteklasse
from ..enum.geraetetyp import Geraetetyp
from ..enum.typ import Typ
from ..utils import postprocess_docstring
from .geschaeftsobjekt import Geschaeftsobjekt

if TYPE_CHECKING:
from ..enum.geraeteklasse import Geraeteklasse
from ..enum.geraetetyp import Geraetetyp


# pylint: disable=too-few-public-methods


Expand All @@ -30,13 +33,13 @@ class Geraet(Geschaeftsobjekt):
"""

typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.GERAET
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.GERAET

#: Die auf dem Gerät aufgedruckte Nummer, die vom MSB vergeben wird.
geraetenummer: Optional[str] = None
#: Bezeichnung des Geräts
bezeichnung: Optional[str] = None
#: Die ßbergreifende Klasse eines Geräts, beispielsweise Wandler
geraeteklasse: Optional[Geraeteklasse] = None
geraeteklasse: Optional["Geraeteklasse"] = None
#: Der speziellere Typ eines Gerätes, beispielsweise Stromwandler
geraetetyp: Optional[Geraetetyp] = None
geraetetyp: Optional["Geraetetyp"] = None
4 changes: 2 additions & 2 deletions src/bo4e/bo/geschaeftsobjekt.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ class Geschaeftsobjekt(BaseModel):
__version__ #: Version der BO-Struktur aka "fachliche Versionierung"
)
# src/_bo4e_python_version.py
typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.GESCHAEFTSOBJEKT #: Der Typ des Geschäftsobjektes
typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.GESCHAEFTSOBJEKT #: Der Typ des Geschäftsobjektes
# bo_typ is used as discriminator f.e. for databases or deserialization

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

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

0 comments on commit d3ccf64

Please sign in to comment.