Source code for pak.dyn_value

"""Code for transforming certain values into dynamic values."""

import abc
from contextlib import contextmanager

from . import util

__all__ = [
    "DynamicValue",
]

[docs]class DynamicValue(abc.ABC): r"""A definition of how to dynamically get one value from another. :class:`.Type`\s and :class:`.Packet`\s have certain attributes whose values can be transformed into something callable-*ish*. :class:`DynamicValue` is the mechanism behind that transformation. To enroll a certain type into the :class:`DynamicValue` machinery, make a subclass of :class:`DynamicValue`, setting the :attr:`_type` attribute to the type in question. Doing so will "enable" the subclass on class initialization. This can be overridden by setting the :attr:`_enabled` attribute explicitly. Alternatively, there are also the :meth:`enable` and :meth:`disable` methods, and the :meth:`context` method for context management. For instance, to enroll :class:`str` into the machinery: .. testcode:: import pak class StringDynamicValue(pak.DynamicValue): _type = str # The initial value is passed to # the constructor. def __init__(self, string): self.string = string # The dynamic value is returned # from the "get" method. # # Here we return the reversed string. def get(self, *, ctx=None): return self.string[::-1] # This will lead to the following behavior: v = pak.DynamicValue("Hello") print(isinstance(v, StringDynamicValue)) print(v.get()) StringDynamicValue.disable() print(pak.DynamicValue("Hello")) .. testoutput:: True olleH Hello Parameters ---------- initial_value The initial value for the :class:`DynamicValue`. Returns ------- any If the type of ``inital_value`` is something for :class:`DynamicValue` to deal with, then an instance of the appropriate subclass will be returned. Otherwise, ``initial_value`` is returned. Attributes ---------- _type : :class:`type` The type for the :class:`DynamicValue` to deal with. _enabled : :class:`bool` Whether the :class:`DynamicValue` is enabled. """ _type = None _enabled = None def __new__(cls, initial_value): for subclass in util.subclasses(DynamicValue): if subclass._enabled and isinstance(initial_value, subclass._type): return subclass(initial_value) return initial_value @classmethod def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) if cls._enabled is None: # NOTE: Calling 'enable' sets the '_enabled' attribute, # which may make this problematic with inheritance. # Presently however we do not care about this potential issue. cls.enable() # Reset '__new__' to a conventional state. if cls.__new__ is DynamicValue.__new__: cls.__new__ = lambda cls, *args, **kwargs: object.__new__(cls)
[docs] @classmethod def enable(cls): """Enables the class to be used in the :class:`DynamicValue` machinery.""" cls._enabled = True
[docs] @classmethod def disable(cls): """Disables the class to be used in the :class:`DynamicValue` machinery.""" cls._enabled = False
[docs] @classmethod @contextmanager def context(cls): """Temporarily enables then disables the class. Examples -------- >>> import pak >>> class StringToIntDynamicValue(pak.DynamicValue): ... _type = str ... def __init__(self, string): ... self.string = string ... def get(self, *, ctx=None): ... return int(self.string) ... >>> with StringToIntDynamicValue.context(): ... print(isinstance(pak.DynamicValue("1"), StringToIntDynamicValue)) ... True >>> pak.DynamicValue("1") '1' """ try: cls.enable() yield finally: cls.disable()
[docs] @abc.abstractmethod def get(self, *, ctx=None): """Gets the dynamic value. Parameters ---------- ctx : :class:`.Packet.Context` or :class:`.Type.Context` The context for the dynamic value. Returns ------- any The dynamic value. """ raise NotImplementedError