Skip to content
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
14 changes: 7 additions & 7 deletions src/bo4e/enum/anrede.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# pylint: disable=missing-module-docstring
from bo4e.enum.strenum import StrEnum
from bo4e.enum.strenum import DocumentedStrEnum


class Anrede(StrEnum):
class Anrede(DocumentedStrEnum):
"""
Übersicht möglicher Anreden, z.B. eines Geschäftspartners.
"""

HERR = "HERR"
FRAU = "FRAU"
EHELEUTE = "EHELEUTE"
FIRMA = "FIRMA"
INDIVIDUELL = "INDIVIDUELL"
HERR = "HERR", "Herr"
FRAU = "FRAU", "Frau"
EHELEUTE = "EHELEUTE", "Eheleute"
FIRMA = "FIRMA", "Firma"
INDIVIDUELL = "INDIVIDUELL", 'Individuell (z.B. "Profx")'
22 changes: 22 additions & 0 deletions src/bo4e/enum/strenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,25 @@ class StrEnum(str, Enum):
"""

# see https://docs.python.org/3/library/enum.html?highlight=strenum#others


# pylint: disable=too-few-public-methods
class DocumentedStrEnum(StrEnum):
"""
A StrEnum that has docstrings attached to its members.
Use it like this:
class MyDocumentedEnum(DocumentedStrEnum):
Foo = "Serialized Foo", "my good docstring of Foo"
Bar = "Serialized Bar", "bar is not foo (this is a docstring)"
Then wherever MyDocumentedEnum is used in BOs or COMs, the attribute will serialize as either "Serialized Foo"
or "Serialized Bar". And inspect.getdocs(MyDocumentedEnum.Foo) will return the respective docstring.
This is unittested.
"""

# see https://stackoverflow.com/a/50473952/10009545
def __new__(cls, value, doc=None):
self = str.__new__(cls, value) # calling super().__new__(value) here would fail
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I tried it with StrEnum but it did not work =(
It seems that it can not find the right ENUM if we use the StrEnum class.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

same for me

self._value_ = value
if doc is not None:
self.__doc__ = doc
return self
91 changes: 91 additions & 0 deletions tests/test_enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import inspect
import re
from pathlib import Path
from typing import List, Optional, TypeVar

import pytest # type:ignore[import]

from bo4e.enum import anrede
from bo4e.enum.anrede import Anrede
from bo4e.enum.strenum import StrEnum


class TestEnums:
"""
A test class that checks the enum construction.
"""

starts_with_whitespace_pattern = re.compile(r"^[\s\n]+")
ends_with_whitespace_pattern = re.compile(r"[\s\n]+$")

TEnum = TypeVar("TEnum", bound=StrEnum)

@staticmethod
def _get_all_enum_classes() -> List[TEnum]:
"""
returns a list of all bo4e.enum classes
"""
arbitrary_enum_module_path = Path(anrede.__file__)
result = []
for python_file in arbitrary_enum_module_path.parent.glob("*.py"):
# don't ask me why. but it works.
enum_module = __import__("bo4e.enum." + python_file.name.split(".")[0])
for _, member in inspect.getmembers(enum_module.enum):
if inspect.ismodule(member):
candidate = inspect.getmembers(member)[0][1]
if inspect.isclass(candidate):
result.append(candidate)
return result

@staticmethod
def _get_class_doc(enum_class: TEnum) -> Optional[str]:
"""
asserts that enum class is a class and returns the class' docstring
"""
assert inspect.isclass(enum_class)
return inspect.getdoc(enum_class)

def test_enum_classes_docstrings(self):
"""
Tests that the docstrings of the enum classes do not start with whitespace or blank lines.
"""
all_enums = TestEnums._get_all_enum_classes()
assert len(all_enums) > 100 # just to be sure we're not using the wrong directory or path
for enum_class in all_enums:
docstring = TestEnums._get_class_doc(enum_class)
assert docstring is not None
assert not TestEnums.starts_with_whitespace_pattern.match(docstring)
assert not TestEnums.ends_with_whitespace_pattern.match(docstring)

@pytest.mark.parametrize(
"enum_member, expected_docstring",
[
pytest.param(Anrede.HERR, "Herr"),
pytest.param(Anrede.INDIVIDUELL, 'Individuell (z.B. "Profx")'),
],
)
def test_enum_member_docstrings_explicitly(self, enum_member: TEnum, expected_docstring: Optional[str]):
"""
Test the docstrings of the enum members explicitly.
if the general approach (using DocumentedStrEnum) works for single members, it will also work for all enums,
which are constructed similarly.
"""
assert inspect.getdoc(enum_member) == expected_docstring

def test_enum_members_are_all_documented(self):
"""
The class docstrings are enforced using pylint but the docstrings of enum members are not covered by pylint.
"""
all_enums = self._get_all_enum_classes()
for enum_class in all_enums:
class_docstring = TestEnums._get_class_doc(enum_class)
for enum_member in enum_class:
member_docstring = inspect.getdoc(enum_member)
assert (
member_docstring != class_docstring
), f"Docstring of Enum member {enum_member} must not be the same as the class docstring"
assert member_docstring is not None
assert member_docstring != ""
assert not TestEnums.starts_with_whitespace_pattern.match(member_docstring)
assert not TestEnums.ends_with_whitespace_pattern.match(member_docstring)
break # proof this works just for the wip anrede