Source code for yaml_provenance._wrapper

"""
WithProvenance wrapper factory — creates provenance-aware subclasses dynamically.
"""

from ._provenance import Provenance
from ._config import get_config


# Registry of dynamically created WithProvenance classes
_wrapper_registry = {}


# ========================================================
# PROVENANCE WRAPPER FACTORY CLASS METHODS AND PROPERTIES
# ========================================================
@classmethod
def wrapper_with_provenance_new(cls, *args, **kwargs):
    """
    ``__new__`` method for WithProvenance classes. Required for ``copy.deepcopy``.
    """
    return super(cls, cls).__new__(cls, args[1])


def wrapper_with_provenance_init(self, value, provenance=None):
    """
    ``__init__`` method for WithProvenance classes. Adds the ``provenance``
    attribute as a ``Provenance`` instance.

    Parameters
    ----------
    value : any
        Value of the object.
    provenance : any
        The provenance information.
    """
    config = get_config()
    if isinstance(provenance, Provenance):
        self._provenance = provenance
    else:
        self._provenance = Provenance(provenance, track_history=config.track_history)
    self.value = value


@property
def prop_provenance(self):
    """Property getter for provenance."""
    return self._provenance


@prop_provenance.setter
def prop_provenance(self, new_provenance):
    """
    Setter for the ``provenance`` property. Ensures the value is a ``Provenance``
    instance.

    Raises
    ------
    ValueError
        If ``new_provenance`` is not a ``Provenance`` object.
    """
    if not isinstance(new_provenance, Provenance):
        raise ValueError(
            "Provenance must be an instance of the provenance.Provenance class!"
        )
    self._provenance = new_provenance


# =======================================================
# CLASSES FOR THE UNSUBCLASSABLE CLASSES (BOOL AND NONE)
# =======================================================
class ProvenanceClassForTheUnsubclassable:
    """
    Base class for types that cannot be subclassed (``bool``, ``NoneType``).
    Stores the ``value`` and ``provenance`` attributes.
    """

    def __repr__(self):
        return f"{self.value}"

    def __bool__(self):
        return bool(self.value)

    def __eq__(self, other):
        if isinstance(other, BoolWithProvenance):
            return self.value == other.value
        return self.value == other

    def __hash__(self):
        return hash(self.value)


# Add the class attributes common to all WithProvenance classes
ProvenanceClassForTheUnsubclassable.__init__ = wrapper_with_provenance_init
ProvenanceClassForTheUnsubclassable.provenance = prop_provenance


[docs] class BoolWithProvenance(ProvenanceClassForTheUnsubclassable): """ Class for emulating ``bool`` behaviour with provenance. ``isinstance(obj, bool)`` returns ``True``. """ @property def __class__(self): return bool
[docs] class NoneWithProvenance(ProvenanceClassForTheUnsubclassable): """ Class for emulating ``None`` behaviour with provenance. ``isinstance(obj, type(None))`` returns ``True``. """ @property def __class__(self): return type(None)
# ================================ # WRAPPER WITH PROVENANCE FACTORY # ================================
[docs] def wrapper_with_provenance_factory(value, provenance=None): """ Factory function that creates provenance-aware wrappers for any value type. For subclassable types, dynamically creates a ``{Type}WithProvenance`` subclass. For ``bool`` and ``NoneType`` (which cannot be subclassed), returns special wrapper instances. For types registered in ``config.custom_type_handlers``, delegates to the registered handler. Parameters ---------- value : any Value to wrap with provenance. provenance : any The provenance information. Returns ------- object The value wrapped with provenance tracking. """ # Avoid circular import from ._dict import DictWithProvenance from ._list import ListWithProvenance PROVENANCE_MAPPINGS = (DictWithProvenance, ListWithProvenance) config = get_config() # Check custom type handlers first value_type = type(value) if value_type in config.custom_type_handlers: return config.custom_type_handlers[value_type](value, provenance) if value_type == bool: return BoolWithProvenance(value, provenance) elif value is None: return NoneWithProvenance(value, provenance) elif isinstance(value, PROVENANCE_MAPPINGS): return value else: subtype = type(value) class_name = f"{subtype}".split("'")[1] class_name = f"{class_name[0].upper()}{class_name[1:]}WithProvenance" if class_name not in _wrapper_registry: _wrapper_registry[class_name] = type( class_name, (subtype,), { "_class_name": class_name, "__new__": wrapper_with_provenance_new, "__init__": wrapper_with_provenance_init, "provenance": prop_provenance, }, ) return _wrapper_registry[class_name](value, provenance)
def get_wrapper_class(class_name): """ Get a dynamically created WithProvenance class by name. Parameters ---------- class_name : str The class name (e.g. ``"StrWithProvenance"``). Returns ------- type or None The class, or ``None`` if not yet created. """ return _wrapper_registry.get(class_name)