From b9226b968f8565b1fdc8a6686ee2962803f818c9 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 31 Jul 2024 21:26:27 +0200 Subject: [PATCH] Move `EdifactFormat` and `EdifactFormatVersion` to `efoli` package (#434) * Move `EdifactFormat` and `EdifactFormatVersion` to `efoli` package * fix requirements.in * upgrade efoli * empty commit * empty commit * bump efoli * empty commit * isort black --- pyproject.toml | 1 + requirements.in | 1 + requirements.txt | 6 +- src/maus/edifact.py | 131 +------------------------ tests/unit_tests/test_edifact_enums.py | 89 +---------------- 5 files changed, 12 insertions(+), 216 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ed75229..422e27f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "attrs>=22.1.0", "marshmallow>=3.18.0", "more_itertools", + "efoli>=0.0.3" # add everything you add in requirements.in here ] dynamic = ["readme", "version"] diff --git a/requirements.in b/requirements.in index dbd30b13..4ffcaa2e 100644 --- a/requirements.in +++ b/requirements.in @@ -5,4 +5,5 @@ more_itertools xmltodict click lark +efoli>=0.0.3 # whenever adding something here, don't forget to also add it to pyproject.toml dependencies diff --git a/requirements.txt b/requirements.txt index 4e0040e7..c42db272 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile requirements.in @@ -8,6 +8,10 @@ attrs==23.2.0 # via -r requirements.in click==8.1.7 # via -r requirements.in +colorama==0.4.6 + # via click +efoli==0.0.3 + # via -r requirements.in lark==1.1.9 # via -r requirements.in lxml==5.2.2 diff --git a/src/maus/edifact.py b/src/maus/edifact.py index 76149462..97af3e6d 100644 --- a/src/maus/edifact.py +++ b/src/maus/edifact.py @@ -2,129 +2,16 @@ This module manages EDIFACT related stuff. It's basically a helper module to avoid stringly typed parameters. """ -import datetime import re -from enum import Enum -from typing import Dict, Optional +from typing import Optional import attrs +from efoli import EdifactFormat, EdifactFormatVersion, get_format_of_pruefidentifikator _PRUEFI_REGEX = r"^[1-9]\d{4}$" pruefidentifikator_pattern = re.compile(_PRUEFI_REGEX) -# pylint: disable=too-few-public-methods -class EdifactFormat(str, Enum): - """ - existing EDIFACT formats - """ - - APERAK = "APERAK" - COMDIS = "COMDIS" #: communication dispute - CONTRL = "CONTRL" #: control messages - IFTSTA = "IFTSTA" #: Multimodaler Statusbericht - INSRPT = "INSRPT" #: Prüfbericht - INVOIC = "INVOIC" #: invoice - MSCONS = "MSCONS" #: meter readings - ORDCHG = "ORDCHG" #: changing an order - ORDERS = "ORDERS" #: orders - ORDRSP = "ORDRSP" #: orders response - PRICAT = "PRICAT" #: price catalogue - QUOTES = "QUOTES" #: quotes - REMADV = "REMADV" #: zahlungsavis - REQOTE = "REQOTE" #: request quote - PARTIN = "PARTIN" #: market partner data - UTILMD = "UTILMD" #: utilities master data - UTILMDG = "UTILMDG" #: utilities master data for 'Gas' - UTILMDS = "UTILMDS" #: utilities master data for 'Strom' - UTILMDW = "UTILMDW" #: utilities master data 'Wasser' - UTILTS = "UTILTS" #: formula - - def __str__(self): - return self.value - - -_edifact_mapping: Dict[str, EdifactFormat] = { - "99": EdifactFormat.APERAK, - "29": EdifactFormat.COMDIS, - "21": EdifactFormat.IFTSTA, - "23": EdifactFormat.INSRPT, - "31": EdifactFormat.INVOIC, - "13": EdifactFormat.MSCONS, - "39": EdifactFormat.ORDCHG, - "17": EdifactFormat.ORDERS, - "19": EdifactFormat.ORDRSP, - "27": EdifactFormat.PRICAT, - "15": EdifactFormat.QUOTES, - "33": EdifactFormat.REMADV, - "35": EdifactFormat.REQOTE, - "37": EdifactFormat.PARTIN, - "11": EdifactFormat.UTILMD, - "25": EdifactFormat.UTILTS, - "91": EdifactFormat.CONTRL, - "92": EdifactFormat.APERAK, - "44": EdifactFormat.UTILMD, # UTILMD for GAS since FV2310 - "55": EdifactFormat.UTILMD, # UTILMD for STROM since FV2310 -} - - -class EdifactFormatVersion(str, Enum): - """ - One format version refers to the period in which an AHB is valid. - """ - - FV2104 = "FV2104" #: valid from 2021-04-01 until 2021-10-01 - FV2110 = "FV2110" #: valid from 2021-10-01 until 2022-04-01 - FV2210 = "FV2210" #: valid from 2022-10-01 onwards ("MaKo 2022", was 2204 previously) - FV2304 = "FV2304" #: valid from 2023-04-01 onwards - FV2310 = "FV2310" #: valid from 2023-10-01 onwards - FV2404 = "FV2404" #: valid from 2024-04-01 onwards - FV2410 = "FV2410" #: valid from 2024-10-01 onwards - FV2504 = "FV2504" #: valid from 2025-04-04 onwards - FV2510 = "FV2510" #: valid from 2025-10-01 onwards - # whenever you add another value here, please also make sure to add its key date to get_edifact_format_version below - - def __str__(self): - return self.value - - -def get_edifact_format_version(key_date: datetime.datetime) -> EdifactFormatVersion: - """ - Retrieves the appropriate Edifact format version applicable for the given key date. - - This function determines the correct Edifact format version by comparing the provided key date - against a series of predefined datetime thresholds. Each threshold corresponds to a specific - version of the Edifact format. - - :param key_date: The date for which the Edifact format version is to be determined. - :return: The Edifact format version valid for the specified key date. - """ - format_version_thresholds = [ - (datetime.datetime(2021, 9, 30, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2104), - (datetime.datetime(2022, 9, 30, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2110), - (datetime.datetime(2023, 3, 31, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2210), - (datetime.datetime(2023, 9, 30, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2304), - (datetime.datetime(2024, 4, 2, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2310), - (datetime.datetime(2024, 9, 30, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2404), - (datetime.datetime(2025, 4, 3, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2410), - (datetime.datetime(2025, 9, 30, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), EdifactFormatVersion.FV2504), - ] - - for threshold_date, version in format_version_thresholds: - if key_date < threshold_date: - return version - - return EdifactFormatVersion.FV2510 - - -def get_current_edifact_format_version() -> EdifactFormatVersion: - """ - returns the edifact_format_version that is valid as of now - """ - tz_aware_now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) - return get_edifact_format_version(tz_aware_now) - - def is_edifact_boilerplate(segment_code: Optional[str]) -> bool: """ returns true iff this segment is not relevant in a sense that it has to be validated or merged with the AHB @@ -134,20 +21,6 @@ def is_edifact_boilerplate(segment_code: Optional[str]) -> bool: return segment_code.strip() in {"UNT", "UNZ"} -def get_format_of_pruefidentifikator(pruefidentifikator: str) -> EdifactFormat: - """ - returns the format corresponding to a given pruefi - """ - if not pruefidentifikator: - raise ValueError("The pruefidentifikator must not be falsy") - if not pruefidentifikator_pattern.match(pruefidentifikator): - raise ValueError(f"The pruefidentifikator '{pruefidentifikator}' is invalid.") - try: - return _edifact_mapping[pruefidentifikator[:2]] - except KeyError as key_error: - raise ValueError(f"No Edifact format was found for pruefidentifikator '{pruefidentifikator}'.") from key_error - - # pylint:disable=unused-argument def _check_that_pruefi_and_format_are_consistent(instance: "EdiMetaData", attribute, value: str): """ diff --git a/tests/unit_tests/test_edifact_enums.py b/tests/unit_tests/test_edifact_enums.py index c55cc7ba..6ee03c4f 100644 --- a/tests/unit_tests/test_edifact_enums.py +++ b/tests/unit_tests/test_edifact_enums.py @@ -1,17 +1,9 @@ -from datetime import datetime, timezone -from typing import Optional, Tuple +from typing import Optional import pytest # type:ignore[import] +from efoli import EdifactFormat, EdifactFormatVersion -from maus.edifact import ( - EdifactFormat, - EdifactFormatVersion, - EdiMetaData, - get_current_edifact_format_version, - get_edifact_format_version, - get_format_of_pruefidentifikator, - is_edifact_boilerplate, -) +from maus.edifact import EdiMetaData, is_edifact_boilerplate class TestEdifact: @@ -19,71 +11,6 @@ class TestEdifact: Tests the edifact module """ - @pytest.mark.parametrize( - "expectation_tuple", - [ - ("11042", EdifactFormat.UTILMD), - ("13002", EdifactFormat.MSCONS), - ("25001", EdifactFormat.UTILTS), - ("44001", EdifactFormat.UTILMD), - ("55001", EdifactFormat.UTILMD), - ], - ) - def test_pruefi_to_format(self, expectation_tuple: Tuple[str, EdifactFormat]): - """ - Tests that the prüfis can be mapped to an EDIFACT format - """ - assert get_format_of_pruefidentifikator(expectation_tuple[0]) == expectation_tuple[1] - - @pytest.mark.parametrize( - "key_date,expected_result", - [ - pytest.param( - datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc), - EdifactFormatVersion.FV2104, - id="Anything before 2021-04-01", - ), - pytest.param(datetime(2021, 5, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2104), - pytest.param(datetime(2021, 10, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2110), - pytest.param(datetime(2022, 7, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2110), - pytest.param(datetime(2022, 10, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2210), - pytest.param(datetime(2022, 10, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2210), - pytest.param(datetime(2023, 12, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2310), - pytest.param(datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2310), - pytest.param( - datetime(2024, 4, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2310 - ), # 2404 is valid form 2024-04-03 onwards - pytest.param(datetime(2024, 4, 2, 22, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2404), - pytest.param(datetime(2024, 9, 30, 21, 59, 59, tzinfo=timezone.utc), EdifactFormatVersion.FV2404), - pytest.param(datetime(2024, 9, 30, 22, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2410), - pytest.param(datetime(2025, 3, 31, 22, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2410), - pytest.param(datetime(2025, 4, 3, 22, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2504), - pytest.param(datetime(2025, 9, 30, 22, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2510), - pytest.param( - datetime(2050, 10, 1, 0, 0, 0, tzinfo=timezone.utc), EdifactFormatVersion.FV2510 - ), # or what ever is the latest version - ], - ) - def test_format_version_from_keydate(self, key_date: datetime, expected_result: EdifactFormatVersion): - actual = get_edifact_format_version(key_date) - assert actual == expected_result - - def test_get_current_format_version(self): - actual = get_current_edifact_format_version() - assert isinstance(actual, EdifactFormatVersion) is True - - @pytest.mark.parametrize( - "illegal_pruefi", - [None, "", "asdas", "01234"], - ) - def test_illegal_pruefis(self, illegal_pruefi: Optional[str]): - """ - Test that illegal pruefis are not accepted - :return: - """ - with pytest.raises(ValueError): - get_format_of_pruefidentifikator(illegal_pruefi) # type:ignore[arg-type] # ok, because this raises an error - def test_edi_meta_data_instantiation(self): actual = EdiMetaData( pruefidentifikator="11042", @@ -112,16 +39,6 @@ def test_edi_meta_data_instantiation_with_error(self): in str(value_error) ) - @pytest.mark.parametrize("pruefi", [pytest.param("10000")]) - def test_pruefi_to_format_not_mapped_exception(self, pruefi: str): - """ - Test that pruefis that are not mapped to an edifact format are not accepted - """ - with pytest.raises(ValueError) as excinfo: - _ = get_format_of_pruefidentifikator(pruefi) - - assert "No Edifact format was found for pruefidentifikator" in excinfo.value.args[0] - @pytest.mark.parametrize( "segment_code,expected_is_boilerplate", [