Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump ibims from 0.1.6 to 0.1.11 #59

Merged
merged 12 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ classifiers = [
]
dependencies = [
"bomf",
"pvframework",
"ibims",
"pvframework>=0.0.8",
"ibims>=0.1.11",
"python-dateutil",
"pydantic[email]",
"more_itertools",
Expand Down Expand Up @@ -72,3 +72,6 @@ sources = ["src"]
# even if they have no @pytest.mark.asyncio marker.
# https://github.com/pytest-dev/pytest-asyncio#auto-mode
asyncio_mode = "auto"

[tool.mypy]
plugins = ["pydantic.mypy"]
4 changes: 2 additions & 2 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bomf
pvframework
ibims
pvframework>=0.0.8
ibims>=0.1.11
python-dateutil
pydantic[email]
more_itertools
Expand Down
10 changes: 4 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile '.\requirements.in'
# pip-compile requirements.in
#
annotated-types==0.5.0
# via pydantic
Expand All @@ -13,9 +13,7 @@ bidict==0.22.1
# bomf
# pvframework
bo4e==0.5.6
# via
# bomf
# ibims
# via bomf
bomf==0.8.1
# via
# -r requirements.in
Expand All @@ -28,7 +26,7 @@ frozendict==2.3.8
# via
# bomf
# pvframework
ibims==0.1.6
ibims==0.1.11
# via -r requirements.in
idna==3.4
# via email-validator
Expand All @@ -44,7 +42,7 @@ networkx==3.1
# via
# bomf
# pvframework
pvframework==0.0.6
pvframework==0.0.8
# via
# -r requirements.in
# bomf
Expand Down
75 changes: 39 additions & 36 deletions src/pvtool/customer_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@
"""
import re
from datetime import date, datetime
from typing import Any, Generator, Optional, TypeAlias
from typing import Any, Generator, Optional, TypeAlias, TypeVar

from bo4e.com.adresse import Adresse
from bo4e.com.externereferenz import ExterneReferenz
from bo4e.enum.anrede import Anrede
from bo4e.enum.landescode import Landescode
from bomf.config import MigrationConfig
from dateutil.relativedelta import relativedelta
from email_validator import validate_email
from ibims.com import Vertragskonto
from ibims.bo4e import Adresse, Anrede, ExterneReferenz, Landescode, VertragskontoCBA, VertragskontoMBA
from ibims.datasets import TripicaCustomerLoaderDataSet
from injector import Module, provider
from more_itertools import first_true
Expand Down Expand Up @@ -48,7 +44,7 @@ def get_externe_referenz(ex_ref_name: str, externe_referenzen: list[ExterneRefer

def check_geschaeftspartner_anrede(anrede: Anrede):
"""
geschaeftspartner_erw.anrede must be one of the following: HERR, FRAU, FIRMA, EHELEUTE
geschaeftspartner.anrede must be one of the following: HERR, FRAU, FIRMA, EHELEUTE
"""
valid_values_strings = {Anrede.HERR, Anrede.FRAU, Anrede.FIRMA, Anrede.EHELEUTE}
if anrede not in valid_values_strings:
Expand All @@ -59,7 +55,7 @@ def check_geschaeftspartner_anrede(anrede: Anrede):

def check_str_is_stripped(string: str):
"""
geschaeftspartner_erw.name1 must not start with whitespace. Further validation is difficult because e.g.
geschaeftspartner.name1 must not start with whitespace. Further validation is difficult because e.g.
there exist names with characters like '.
And if the name is a company it could even contain digits or other characters e.g. 'edi@energy'.
"""
Expand All @@ -69,7 +65,7 @@ def check_str_is_stripped(string: str):

def check_geschaeftspartner_name3(name3: Optional[str] = None):
"""
geschaeftspartner_erw.name2 must not start with whitespace. Further validation is difficult because e.g.
geschaeftspartner.name2 must not start with whitespace. Further validation is difficult because e.g.
there exist names with characters like '.
And if the name is a company it could even contain digits or other characters e.g. 'edi@energy'.
"""
Expand All @@ -80,7 +76,7 @@ def check_geschaeftspartner_name3(name3: Optional[str] = None):

def check_e_mail(e_mail: Optional[str] = None):
"""
geschaeftspartner_erw.e_mail_adresse must match the regex pattern `REGEX_E_MAIL`.
geschaeftspartner.e_mail_adresse must match the regex pattern `REGEX_E_MAIL`.
"""
if not e_mail:
return
Expand All @@ -89,7 +85,7 @@ def check_e_mail(e_mail: Optional[str] = None):

def check_extern_customer_id(externe_referenzen: list[ExterneReferenz]):
"""
geschaeftspartner_erw.externe_referenzen -> customerID has to start with 2 followed by 8 digits.
geschaeftspartner.externe_referenzen -> customerID has to start with 2 followed by 8 digits.
"""
customer_id = get_externe_referenz("customerID", externe_referenzen)
if customer_id is None:
Expand Down Expand Up @@ -163,7 +159,7 @@ def check_date_in_past_bankverbindung(is_sepa_zahler: bool, past_date: Optional[

def check_geschaeftspartner_geburtsdatum(geburtsdatum: datetime):
"""
geschaeftspartner_erw.geburtsdatum must be at least 18 years ago in the past but not earlier than 1900-01-01.
geschaeftspartner.geburtsdatum must be at least 18 years ago in the past but not earlier than 1900-01-01.
"""
config = migration_config()
birthday_date = geburtsdatum.astimezone(_berlin).date()
Expand Down Expand Up @@ -214,12 +210,22 @@ def check_address_fields(address: Adresse):
Postleitzahl w w w
Ort w w w
"""
param_path = param("address").param_id
_ = (
required_field(address, "ort", str),
required_field(address, "postleitzahl", str),
required_field(address, "ort", str, param_base_path=param_path),
required_field(address, "postleitzahl", str, param_base_path=param_path),
)
# pylint: disable=protected-access
Adresse._strasse_xor_postfach(address.model_dump()) # type:ignore[operator]
# Taken from the old implementation of the Address validator. See
# https://github.com/bo4e/BO4E-python/blob/4157dab6436546ba5a911b9b3767cd312ba41e97/src/bo4e/com/adresse.py#L53
if (
address.strasse
and address.hausnummer
and not address.postfach
or not address.strasse
and not address.hausnummer
):
return
raise ValueError('You have to define either "strasse" and "hausnummer" or "postfach".')


def check_postleitzahl(postleitzahl: str):
Expand Down Expand Up @@ -323,7 +329,12 @@ def iter_contract_id_dict(some_dict: dict[str, Any]) -> Generator[tuple[Any, str
return ((value, f"[contract_id={key}]") for key, value in some_dict.items())


def iter_vertragskonten(vertragskonten: list[Vertragskonto]) -> Generator[tuple[Vertragskonto, str], None, None]:
VertragskontoT = TypeVar("VertragskontoT", VertragskontoMBA, VertragskontoCBA)


def iter_vertragskonten(
vertragskonten: list[VertragskontoT],
) -> Generator[tuple[VertragskontoT, str], None, None]:
"""
This function is used for `Query().iter()` to iterate over a dictionary. The values of the dictionary are returned
and `vertragskonto.ouid` is used for tracking for proper `ValidationError`s.
Expand All @@ -345,43 +356,35 @@ def customer_validation_manager(self, config: MigrationConfig) -> ValidationMana
config, manager_id="CustomerLoader"
)
customer_manager.register(
PathMappedValidator(validate_geschaeftspartner_anrede, {"anrede": "geschaeftspartner_erw.anrede"})
)
customer_manager.register(
PathMappedValidator(validate_str_is_stripped, {"string": "geschaeftspartner_erw.name1"})
PathMappedValidator(validate_geschaeftspartner_anrede, {"anrede": "geschaeftspartner.anrede"})
)
customer_manager.register(PathMappedValidator(validate_str_is_stripped, {"string": "geschaeftspartner.name1"}))
customer_manager.register(PathMappedValidator(validate_str_is_stripped, {"string": "geschaeftspartner.name2"}))
customer_manager.register(
PathMappedValidator(validate_str_is_stripped, {"string": "geschaeftspartner_erw.name2"})
)
customer_manager.register(
PathMappedValidator(validate_geschaeftspartner_name3, {"name3": "geschaeftspartner_erw.name3"})
)
customer_manager.register(
PathMappedValidator(validate_e_mail, {"e_mail": "geschaeftspartner_erw.e_mail_adresse"})
PathMappedValidator(validate_geschaeftspartner_name3, {"name3": "geschaeftspartner.name3"})
)
customer_manager.register(PathMappedValidator(validate_e_mail, {"e_mail": "geschaeftspartner.e_mail_adresse"}))
customer_manager.register(
PathMappedValidator(
validate_extern_customer_id, {"externe_referenzen": "geschaeftspartner_erw.externe_referenzen"}
validate_extern_customer_id, {"externe_referenzen": "geschaeftspartner.externe_referenzen"}
)
)
customer_manager.register(
PathMappedValidator(validate_date_in_past_required, {"past_date": "geschaeftspartner_erw.erstellungsdatum"})
PathMappedValidator(validate_date_in_past_required, {"past_date": "geschaeftspartner.erstellungsdatum"})
)
customer_manager.register(
PathMappedValidator(
validate_geschaeftspartner_geburtsdatum, {"geburtsdatum": "geschaeftspartner_erw.geburtstag"}
validate_geschaeftspartner_geburtsdatum, {"geburtsdatum": "geschaeftspartner.geburtstag"}
)
)
customer_manager.register(
PathMappedValidator(validate_telefonnummer, {"telefonnummer": "geschaeftspartner_erw.telefonnummer_privat"})
PathMappedValidator(validate_telefonnummer, {"telefonnummer": "geschaeftspartner.telefonnummer_privat"})
)
customer_manager.register(
PathMappedValidator(
validate_telefonnummer, {"telefonnummer": "geschaeftspartner_erw.telefonnummer_geschaeft"}
)
PathMappedValidator(validate_telefonnummer, {"telefonnummer": "geschaeftspartner.telefonnummer_geschaeft"})
)
customer_manager.register(
PathMappedValidator(validate_telefonnummer, {"telefonnummer": "geschaeftspartner_erw.telefonnummer_mobil"})
PathMappedValidator(validate_telefonnummer, {"telefonnummer": "geschaeftspartner.telefonnummer_mobil"})
)
customer_manager.register(
QueryMappedValidator(
Expand Down
7 changes: 1 addition & 6 deletions src/pvtool/network_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
import re
from typing import Iterator

from bo4e.com.zaehlwerk import Zaehlwerk
from bo4e.enum.kundentyp import Kundentyp
from bo4e.enum.rollencodetyp import Rollencodetyp
from bo4e.enum.sparte import Sparte
from bo4e.enum.tarifart import Tarifart
from bo4e.enum.zaehlerauspraegung import Zaehlerauspraegung
from ibims.bo4e import Kundentyp, Rollencodetyp, Sparte, Tarifart, Zaehlerauspraegung, Zaehlwerk
from ibims.datasets import TripicaNetworkLoaderDataSet
from injector import Module, provider
from pvframework import PathMappedValidator, Query, QueryMappedValidator, ValidationManager, Validator
Expand Down
2 changes: 1 addition & 1 deletion src/pvtool/resource_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import re

from bo4e.enum.sparte import Sparte
from ibims.bo4e import Sparte
from ibims.datasets import TripicaResourceLoaderDataSet
from injector import Module, provider
from pvframework import PathMappedValidator, ValidationManager, Validator
Expand Down
53 changes: 53 additions & 0 deletions unittests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import TypeVar

from pvframework.errors import ValidationError

SubT = TypeVar("SubT")
BaseT = TypeVar("BaseT")


def intersection_with_contains_str(
set_sub_container: set[SubT], set_base_container: set[BaseT]
) -> tuple[set[SubT], set[BaseT]]:
r"""
Calculates all elements from both sets "intersecting" in the following manner:
Let's call `set_sub_container` A and `set_base_container` B.
A consists of elements a1, a2, a3, ...
B consists of elements b1, b2, b3, ...

It returns a tuple two sets:
The first set contains all elements from A that are contained in at least one element of B.
I.e. {a_i ∈ A | ∃ b_j ∈ B : a_i ⊆ b_j}
LaTeX-Code: \left\{a_i \in A | \exists_{b_j \in B}: a_i \subseteq b_j\right\}
The second set contains all elements from B that have at least one element of A being a substring of the element
from B.
I.e. {b_j ∈ B | ∃ a_i ∈ A : b_j ⊇ a_i}
LaTeX-Code: \left\{b_j \in B | \exists_{a_i \in A}: b_j \supseteq a_i\right\}
"""
set_sub_container_intersection = set()
set_base_container_intersection = set()
for item_base in set_base_container:
for item_sub in set_sub_container:
if str(item_sub) in str(item_base):
set_sub_container_intersection.add(item_sub)
set_base_container_intersection.add(item_base)
break

return set_sub_container_intersection, set_base_container_intersection


def assert_full_error_coverage(expected_errors: set[str], all_errors: set[ValidationError]):
"""
Asserts that all expected errors are found in the actual errors and vice versa.
The error messages in `expected_errors` don't have to be exact matches.
Every actual error message must have at least one element in `expected_errors` being
a substring of the actual error message.
"""
expected_errors_found, actual_errors_in_expected_list = intersection_with_contains_str(expected_errors, all_errors)
if len(expected_errors) != len(expected_errors_found) or len(all_errors) != len(actual_errors_in_expected_list):
expected_errors_not_found = expected_errors - expected_errors_found
uncovered_actual_errors = all_errors - actual_errors_in_expected_list
raise AssertionError(
f"Expected errors not found: {expected_errors_not_found}\n"
f"Actual errors not covered from expected list: {uncovered_actual_errors}"
)
Loading
Loading