Source code for domprob.announcements.metadata
from collections.abc import Callable, Generator
from dataclasses import dataclass
from typing import Any
[docs]
@dataclass(frozen=True)
class AnnouncementMetadataEntry:
"""Represents metadata entry for an announcement's method. Includes
the instrument class and its requirement status.
Args:
instrument_cls (type[`BaseInstrument`]): The type of
instrument for which the announcements should be executed.
required (`bool`), optional: Whether the instrument instance
at runtime is required or optional for the announcements.
Defaults to `True` if not provided during instantiation.
Examples:
>>> class SomeInstrument:
... pass
...
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> entry = metadata.AnnouncementMetadataEntry(SomeInstrument, required=False)
>>> entry
AnnouncementMetadataEntry(instrument_cls=<class '...SomeInstrument'>, required=False)
>>> entry.instrument_cls
<class '...SomeInstrument'>
>>> entry.required
False
"""
instrument_cls: type[Any]
required: bool
[docs]
class AnnouncementMetadata:
"""Stores and manages metadata for an instance method.
Args:
method (`Callable[..., Any]`): The method for which the
metadata is to be managed.
Examples:
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta = metadata.AnnouncementMetadata(Foo.bar)
>>>
>>> meta
AnnouncementMetadata(method=<function Foo.bar at 0x...>)
"""
# The attribute name where the metadata will be saved to on the
# method.
METADATA_ATTR: str = "__announcement_metadata__"
def __init__(self, method: Callable[..., Any]) -> None:
while hasattr(method, "__wrapped__"): # Get original non-wrapped
method = getattr(method, "__wrapped__")
self._method = method
[docs]
def __len__(self) -> int:
"""Returns the number of metadata entries.
Returns:
int: The number of metadata entries recorded for the
method.
Examples:
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta = metadata.AnnouncementMetadata(Foo.bar)
>>>
>>> len(meta)
0
>>> # Define an instrument
>>> class SomeInstrument:
... pass
...
>>> meta.add(SomeInstrument, required=True)
AnnouncementMetadata(method=<function Foo.bar at 0x...>)
>>> len(meta)
1
"""
return len(getattr(self._method, self.METADATA_ATTR, []))
[docs]
def __iter__(self) -> Generator[AnnouncementMetadataEntry, None, None]:
"""Iterates over all metadata entries recorded for the method.
Yields:
AnnoMetadataItem: Metadata items associated with the
method.
Examples:
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta = metadata.AnnouncementMetadata(Foo.bar)
>>>
>>> # Define an instrument
>>> class SomeInstrument:
... pass
...
>>> # Add entries to the metadata
>>> meta.add(SomeInstrument, True).add(SomeInstrument, False)
AnnouncementMetadata(method=<function Foo.bar at 0x...>)
>>>
>>> meta_iter = iter(meta)
>>> next(meta_iter)
AnnouncementMetadataEntry(instrument_cls=<class '...SomeInstrument'>, required=True)
>>> next(meta_iter)
AnnouncementMetadataEntry(instrument_cls=<class '...SomeInstrument'>, required=False)
"""
yield from tuple(getattr(self._method, self.METADATA_ATTR, []))
[docs]
def __eq__(self, other: Any) -> bool:
"""Equality operator to check if two `AnnouncementMetadata`
instances are equivalent.
Args:
other (Any): The object to compare with the current
`AnnouncementMetadata` instance. Typically expected
to be another `AnnouncementMetadata` object.
Returns:
bool: Returns `True` if both operands reference the
metadata of the same instance method
Examples:
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta_1 = metadata.AnnouncementMetadata(Foo.bar)
>>> meta_1 == "string"
False
>>> meta_2 = metadata.AnnouncementMetadata(Foo.bar)
>>> meta_1 == meta_2
True
>>>
>>> # Define an instrument
>>> class SomeInstrument:
... pass
...
>>> meta_1.add(SomeInstrument, True)
AnnouncementMetadata(method=<function Foo.bar at 0x...>)
>>> meta_1 == meta_2 # Both reference the same method
True
"""
if not isinstance(other, AnnouncementMetadata):
return False
return self._method == other._method
[docs]
def add(self, instrument: Any, required: bool) -> "AnnouncementMetadata":
"""Adds an announcements metadata entry to the method.
Args:
instrument (type[`BaseInstrument`]): The instrument class
to add to the method's metadata.
required (`bool`): Whether the instrument is required.
Returns:
AnnouncementMetadata: The updated metadata instance.
Examples:
>>> # Define a class with a method
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta = metadata.AnnouncementMetadata(Foo.bar)
>>>
>>> len(meta)
0
>>> # Define an instrument
>>> class SomeInstrument:
... pass
...
>>> meta.add(SomeInstrument, required=True)
AnnouncementMetadata(method=<function Foo.bar at 0x...>)
>>> len(meta)
1
"""
item = AnnouncementMetadataEntry(instrument, required=required)
meth_metadata = list(self)
meth_metadata.append(item)
setattr(self._method, self.METADATA_ATTR, meth_metadata)
return self
[docs]
def __repr__(self) -> str:
"""Returns a string representation of the metadata instance.
Returns:
str: The string representation.
Examples:
>>> # Define a class with a method to decorate
>>> class Foo:
... def bar(self):
... pass
...
>>> # Create metadata for the method
>>> from domprob.announcements import metadata
>>> meta = metadata.AnnouncementMetadata(Foo.bar)
>>> repr(meta)
'AnnouncementMetadata(method=<function Foo.bar at 0x...>)'
"""
return f"{self.__class__.__name__}(method={self._method!r})"