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 2 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
8 changes: 3 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.9
# 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.7
# 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
47 changes: 47 additions & 0 deletions unittests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from collections.abc import Container, Sequence
from typing import Iterable, TypeVar

from pvframework.errors import ValidationError

T1 = TypeVar("T1")
T2 = TypeVar("T2")


def intersection_with_contains_str(
set_sub_container: Iterable[T1], set_base_container: Iterable[T2]
) -> tuple[list[T1], list[T2]]:
"""
Returns the intersection of two sets, but only if the intersection is not empty.
:param set_sub_container: the set to be checked for intersection
:param set_base_container: the set to be checked for intersection
:return: the intersection of the two sets, if the intersection is not empty
"""
set_sub_container = list(set_sub_container)
set_base_container = list(set_base_container)
set_sub_container_intersection = []
set_base_container_intersection = []
for item_sub in set_sub_container:
set_base_remove_list = []
for item_base in set_base_container:
if str(item_sub) in str(item_base):
set_sub_container_intersection.append(item_sub)
set_base_container_intersection.append(item_base)
set_base_remove_list.append(item_base)
break
for item_base in set_base_remove_list:
set_base_container.remove(item_base)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allein aus dem im docstring beschriebenen zweck versteh ich nicht, warum du nicht die intersection von set verwendest oder sogar isdisjoint? (letzteres liest sich ganz ähnlich... hab's aber nicht im detail geprüft). wenn das nen grund hat, kannste ihn ja kommentieren

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das Problem in principle: Zwei Mengen A und B, die jeweils Submengen beinhalten A_i und B_j. Finde alle A_i für die gilt: Es existiert mindestens ein B_j, sodass A_i ⊆ B_j ist.
Oder mehr am Beispiel: Die tatsächlichen Fehlermeldungen sind superlang. Daher hab ich in die Menge der expected_errors nur relevante Auszüge reingeschrieben. Da hilft mir dann die normale intersection leider nicht.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aber ich schreib das nochmal besser in den docstring.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return set_sub_container_intersection, set_base_container_intersection


def assert_full_error_coverage(expected_errors: Sequence[str], all_errors: Sequence[ValidationError]):
expected_errors_found, actual_errors_from_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_from_expected_list):
expected_errors_not_found = list(set(expected_errors) - set(expected_errors_found))
uncovered_actual_errors = list(set(all_errors) - set(actual_errors_from_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