Source code for domprob.announcements.instruments

from __future__ import annotations
from collections.abc import Callable, Generator
from typing import Any, TypeVar, Generic

from domprob.announcements.metadata import AnnouncementMetadata

# Typing helpers
_InstruCls = TypeVar("_InstruCls", bound=type[Any])
_InstrumentClsGen = Generator[_InstruCls, None, None]
_InstrumentTupleClsGen = Generator[tuple[_InstruCls, bool], None, None]


[docs] class Instruments(Generic[_InstruCls]): """Manages and provides access to instrument classes for a decorated method's metadata. Args: metadata (`AnnouncementMetadata`): The metadata object managing the associated method's metadata. 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) >>> >>> # Access metadata instruments >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments(meta) >>> >>> instruments Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) """ def __init__(self, metadata: AnnouncementMetadata) -> None: self._metadata = metadata
[docs] def __iter__(self) -> _InstrumentTupleClsGen: """Iterates over all instruments. Yields: _InstrumentTupleClsGen: Instrument classes associated with the method's metadata. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument >>> class SomeInstrument: ... pass ... >>> instruments.record(SomeInstrument, True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> list(instruments) [(<class 'domprob.announcements.instruments.SomeInstrument'>, True)] """ yield from ((m.instrument_cls, m.required) for m in self._metadata)
[docs] def __len__(self) -> int: """Returns the number of instrument entries associated with the method's metadata. This method provides the total count of all instrument classes recorded in the metadata for the associated method. Returns: int: The total number of instrument classes recorded. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> # Initially, no instruments are recorded >>> len(instruments) 0 >>> # Define an instrument >>> class SomeInstrument: ... pass ... >>> # Record the instrument >>> instruments.record(SomeInstrument, required=True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> len(instruments) 1 """ return len(self._metadata)
[docs] def __eq__(self, other: Any) -> bool: """ Checks equality between the current `Instruments` instance and another object. This method determines whether the provided object is an instance of `Instruments` and whether their associated metadata are equal. Two `Instruments` instances ar considered equal if they manage the same `AnnouncementMetadata`. Args: other (Any): The object to compare with the current `Instruments` instance. Returns: bool: `True` if the provided object is an `Instruments` instance and their metadata are equal. Examples: >>> # Define a class with a method >>> class Foo: ... def bar(self): ... pass ... >>> # Create Instruments instances for the same method >>> from domprob.announcements.instruments import Instruments >>> instruments1 = Instruments.from_method(Foo.bar) >>> instruments2 = Instruments.from_method(Foo.bar) >>> >>> instruments1 == instruments2 True >>> # Create Instruments instance for a different method >>> class Baz: ... def qux(self): ... pass ... >>> instruments3 = Instruments.from_method(Baz.qux) >>> instruments1 == instruments3 False >>> # Compare with an unrelated object >>> instruments1 == "Not an Instruments instance" False """ if not isinstance(other, Instruments): return False return self._metadata == other._metadata
[docs] @classmethod def from_method(cls, method: Callable[..., Any]) -> Instruments: """Creates an Instruments instance from a method. Provides a convenient way to initialise an `Instruments` object directly from a method without explicitly creating an `AnnoMetadata` instance. Args: method (`Callable[..., Any]`): The method for which the instruments should be managed. Returns: Instruments: An `Instruments` instance for managing the method's metadata. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create an Instruments instance directly from the method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> instruments Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) """ return cls(AnnouncementMetadata(method))
@property def non_req_instrums(self) -> _InstrumentClsGen: """Generator yielding supported instrument classes marked as not required in the method's metadata. Yields: TInstrumentClsGen: Non-required instrument classes. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create an Instruments instance directly from the method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument class >>> class SomeInstrument: ... pass ... >>> # Add a required instrument >>> instruments.record(SomeInstrument, required=True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> >>> list(instruments.non_req_instrums) [] >>> instruments.record(SomeInstrument, required=False) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> list(instruments.non_req_instrums) [<class '...SomeInstrument'>] """ yield from (m.instrument_cls for m in self._metadata if not m.required) @property def req_instrums(self) -> _InstrumentClsGen: """Generator yielding supported instrument classes marked as required in the method's metadata. Yields: TInstrumentClsGen: Required instrument classes. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create an Instruments instance directly from the method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument class >>> class SomeInstrument: ... pass ... >>> instruments.record(SomeInstrument, required=False) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> list(instruments.req_instrums) [] >>> instruments.record(SomeInstrument, required=True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> list(instruments.req_instrums) [<class '...SomeInstrument'>] """ yield from (m.instrument_cls for m in self._metadata if m.required)
[docs] def is_required(self, instrument_cls: _InstruCls) -> bool: """Checks if any recorded instrument of the same instrument type given is required. Args: instrument_cls (`TInstruCls`): The instrument type to check against the method's metadata. Returns: bool: `True` if any instrument of the given type is marked as required, otherwise `False`. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument class >>> class SomeInstrument: ... pass ... >>> instruments.is_required(SomeInstrument) False >>> instruments.record(SomeInstrument, False) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> instruments.record(SomeInstrument, True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> instruments.is_required(SomeInstrument) True """ for instru in self._metadata: if (instru.instrument_cls is instrument_cls) and instru.required: return True # return early if required instrument found return False
[docs] def record(self, instrument: _InstruCls, required: bool) -> "Instruments": """Adds an instrument entry to the method's metadata. Args: instrument (type[`BaseInstrument`]): The instrument class to add to the method's metadata. required (`bool`): Whether the instrument is required. Returns: Instruments: The updated `Instruments` instance. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for the method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument class >>> class SomeInstrument: ... pass ... >>> # Add a required instrument >>> instruments.record(SomeInstrument, required=True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> # Add a non-required instrument >>> instruments.record(SomeInstrument, required=False) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) """ self._metadata.add(instrument, required) return self
[docs] def supported(self, required: bool | None = None) -> _InstrumentClsGen: """Yields supported instrument classes filtered by requirement. Args: required (`bool`, optional): If `True`, yields only required instruments. If `False`, yields only non-required instruments. If `None`, yields all instruments. Defaults to `None`. Yields: TInstrumentClsGen: Instrument classes that match the filter criteria. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> >>> # Define an instrument class >>> class SomeInstrument: ... pass ... >>> # Add instruments >>> instruments.record(SomeInstrument, required=True) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> >>> # Filter instruments based on their requirement status >>> list(instruments.supported(True)) [<class '...SomeInstrument'>] >>> list(instruments.supported(False)) [] >>> instruments.record(SomeInstrument, required=False) Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>)) >>> list(instruments.supported()) [<class '...SomeInstrument'>, <class '...SomeInstrument'>] """ if required is None: yield from (m.instrument_cls for m in self._metadata) elif not required: yield from self.non_req_instrums else: yield from self.req_instrums
[docs] def __repr__(self) -> str: """Returns a string representation of the Instruments instance. Returns: str: The string representation of the Instruments object. Examples: >>> # Define a class with a method to decorate >>> class Foo: ... def bar(self): ... pass ... >>> # Create instruments handler for method >>> from domprob.announcements.instruments import Instruments >>> instruments = Instruments.from_method(Foo.bar) >>> repr(instruments) 'Instruments(metadata=AnnouncementMetadata(method=<function Foo.bar at 0x...>))' """ return f"{self.__class__.__name__}(metadata={self._metadata!r})"