Source code for phenotypic._shared_modules._measurement_info
"""Base class for creating measurement information enumerations.
This class provides a standardized way to define measurement types with categories,
labels, and descriptions. It combines Enum functionality with string behavior,
automatically prefixing labels with category names.
Examples
--------
Create a custom measurement enumeration:
>>> from phenotypic._shared_modules._measurement_info import MeasurementInfo
>>>
>>> class COLOR(MeasurementInfo):
... @classmethod
... def category(cls):
... return 'Color'
...
... RED = ('Red', 'Red channel intensity')
... GREEN = ('Green', 'Green channel intensity')
... BLUE = ('Blue', 'Blue channel intensity')
Access measurement information:
>>> COLOR.RED
<Color_Red: 'Color_Red'>
>>> str(COLOR.RED)
'Color_Red'
>>> COLOR.RED.label
'Red'
>>> COLOR.RED.desc
'Red channel intensity'
>>> COLOR.RED.CATEGORY
'Color'
Get all labels and headers:
>>> COLOR.get_labels()
['Red', 'Green', 'Blue']
>>> COLOR.get_headers()
['Color_Red', 'Color_Green', 'Color_Blue']
Generate RST documentation table:
>>> print(COLOR.rst_table())
.. list-table:: COLOR
:header-nrows: 1
<BLANKLINE>
* - Name
- Description
* - ``Red``
- Red channel intensity
* - ``Green``
- Green channel intensity
* - ``Blue``
- Blue channel intensity
Append documentation to a class docstring:
>>> class ColorProcessor:
... '''Processes color measurements.'''
... pass
>>>
>>> ColorProcessor.__doc__ = COLOR.append_rst_to_doc(ColorProcessor)
>>> print(ColorProcessor.__doc__)
Processes color measurements.
<BLANKLINE>
.. list-table:: COLOR
:header-nrows: 1
<BLANKLINE>
* - Name
- Description
* - ``Red``
- Red channel intensity
* - ``Green``
- Green channel intensity
* - ``Blue``
- Blue channel intensity
"""
from enum import Enum
from textwrap import dedent
[docs]
class MeasurementInfo(str, Enum):
# Subclasses must implement this
[docs]
@classmethod
def category(cls) -> str:
raise NotImplementedError
# Public, instance-level property (what you wanted to keep)
@property
def CATEGORY(self) -> str:
return type(self).category()
def __new__(cls, label: str, desc: str | None = None):
cat = cls.category() # use classmethod here
full = f"{cat}_{label}"
obj = str.__new__(cls, full)
obj._value_ = full
obj.label = label
obj.desc = desc if desc else ""
obj.pair = (label, obj.desc)
return obj
def __str__(self):
return self._value_
[docs]
@classmethod
def get_labels(cls):
return [m.label for m in cls]
[docs]
@classmethod
def rst_table(
cls,
*,
title: str | None = None,
header: tuple[str, str] = ("Name", "Description"),
) -> str:
title = title or cls.__name__
left, right = header
lines = [
f".. list-table:: Category: **{title}**",
" :header-rows: 1",
"",
f" * - {left}",
f" - {right}",
]
for m in cls:
lines += [
f" * - ``{m.label}``",
f" - {m.desc}",
]
return dedent("\n".join(lines))
[docs]
@classmethod
def append_rst_to_doc(cls, module) -> str:
"""
returns a string with the RST table appended to the module docstring.
"""
if isinstance(module, str):
return module + "\n\n" + cls.rst_table()
else:
return module.__doc__ + "\n\n" + cls.rst_table()