Skip to content

Commit

Permalink
✨Introduce Bo4eDataSet (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
hf-kklein authored Dec 6, 2022
1 parent c543a56 commit d159863
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ include_package_data = True
python_requires = >=3.10
install_requires =
attrs
bo4e
# write here line by line the dependencies for your package

[options.packages.find]
Expand Down
74 changes: 74 additions & 0 deletions src/bomf/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
general data models for migrations
"""
import enum
from typing import Generic, Iterable, Optional, Protocol, Type, TypeVar, Union

import attrs
from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt
from bo4e.com.com import COM

_SpecificBusinessObject = TypeVar("_SpecificBusinessObject", bound=Geschaeftsobjekt)
"""
an arbitrary but fixed business object type
"""

_SpecificCom = TypeVar("_SpecificCom", bound=COM)
"""
an arbitrary but fixed COM type
"""

Bo4eTyp = Union[_SpecificBusinessObject, _SpecificCom]


# pylint:disable=too-few-public-methods
@attrs.define(kw_only=True, auto_attribs=True)
class BusinessObjectRelation:
"""
A business object relation describes the relation between two business object.
E.g. a relation could have the type "has_melo" where relation_part_a is a bo4e.bo.Vertrag
and relation_part_b is a bo4e.bo.Messlokation. Some relations are already defined in BO4E itself (e.g MaLo/MeLo)
or MeLo/Address.
The idea is to not enforce too much of a structure to the downstream code but still push coders to think about
necessary relation information.
"""

relation_type: enum.Enum = attrs.field()
"""
The relation type describes how two business objects relate to each other.
This is not (only) about cardinality. It's about being able to model different relations between objects.
Think about e.g. a business partner and an address: The relation could be:
- the address is the residential address of the business partner
- the address is the invoice address of the business partner
- the address is the place where the business partner was born
All these relation types are 1:1 relations between business partners and adresses, yet they all carry different
meaning which we'd like to distinguish in our data.
"""
relation_part_a: Bo4eTyp = attrs.field()
"""
one Business Object or COM
"""

relation_part_b: Bo4eTyp = attrs.field()
"""
another Business Object or COM
"""


class Bo4eDataSet(Protocol):
"""
A BO4E data set is a collection of Business Objects that relate to each other.
This class just defines methods that any bo4e data set should implement (via structural subtyping) without forcing
the data sets to inherit from a common base class.
"""

def get_relations(self) -> Iterable[BusinessObjectRelation]:
"""
returns all relations between the business objects
"""

def get_business_object(self, bo_type: Type[Bo4eTyp], specification: Optional[str] = None) -> Bo4eTyp:
"""
Returns a business object of the provided type from the collection.
If the type alone is not unique, you can provide an additional specification.
"""
47 changes: 47 additions & 0 deletions unittests/test_bo4e_data_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import enum
from typing import Iterable, Optional, Type

import pytest # type:ignore[import]
from bo4e.bo.geschaeftspartner import Geschaeftspartner
from bo4e.com.adresse import Adresse
from bo4e.enum.strenum import StrEnum

from bomf.model import Bo4eDataSet, Bo4eTyp, BusinessObjectRelation


class _GeschaeftspartnerAdresseRelation(enum.Enum):
HAS_LIEFERANSCHRIFT = 1
HAS_RECHNUNGSANSCHRIFT = 2
HAS_GEBURTSORT = 3


class _ExampleDataSet:
def __init__(self):
self.business_partner = Geschaeftspartner.construct(name1="Müller", name2="Hans")
self.address = Adresse.construct(strasse="Rechnungsstrasse", hausnummer="5")

def get_relations(self) -> Iterable[BusinessObjectRelation]:
return [
BusinessObjectRelation(
relation_type=_GeschaeftspartnerAdresseRelation.HAS_LIEFERANSCHRIFT,
relation_part_a=self.business_partner,
relation_part_b=self.address,
)
]

def get_business_object(self, bo_type: Type[Bo4eTyp], specification: Optional[str] = None) -> Bo4eTyp:
# pyling:disable=fixme
# todo: find out how to allow the static type checker to not complain about the "dynamic" type
if bo_type == Geschaeftspartner:
return self.business_partner # type:ignore[return-value]
if bo_type == Adresse:
return self.address # type:ignore[return-value]
raise NotImplementedError(f"The bo type {bo_type} is not implemented")


class TestBo4eDataSet:
async def test_example_data_set(self):
dataset: Bo4eDataSet = _ExampleDataSet()
assert len(list(dataset.get_relations())) == 1
assert isinstance(dataset.get_business_object(Geschaeftspartner), Geschaeftspartner)
assert isinstance(dataset.get_business_object(Adresse), Adresse)

0 comments on commit d159863

Please sign in to comment.