Skip to content

Commit

Permalink
Move EdifactFormat and EdifactFormatVersion to efoli package (#434
Browse files Browse the repository at this point in the history
)

* Move `EdifactFormat` and `EdifactFormatVersion` to `efoli` package

* fix requirements.in

* upgrade efoli

* empty commit

* empty commit

* bump efoli

* empty commit

* isort black
  • Loading branch information
hf-kklein authored Jul 31, 2024
1 parent 59e8597 commit b9226b9
Show file tree
Hide file tree
Showing 5 changed files with 12 additions and 216 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
131 changes: 2 additions & 129 deletions src/maus/edifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
"""
Expand Down
89 changes: 3 additions & 86 deletions tests/unit_tests/test_edifact_enums.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,16 @@
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:
"""
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",
Expand Down Expand Up @@ -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",
[
Expand Down

0 comments on commit b9226b9

Please sign in to comment.