"""base package class; instances should derive from this.
Right now, doesn't provide much, need to change that down the line
"""
__all__ = ("base", "wrapper", "dynamic_getattr_dict", "DynamicGetattrSetter")
import itertools
from snakeoil import klass, sequences
from .. import exceptions as base_errors
from ..operations import format
from . import errors
[docs]
class base(klass.SlotsPicklingMixin, metaclass=klass.immutable_instance):
built = False
configurable = False
_operations = format.operations
__slots__ = ("__weakref__",)
_get_attr = {}
@property
def versioned_atom(self):
raise NotImplementedError(self, "versioned_atom")
@property
def unversioned_atom(self):
raise NotImplementedError(self, "unversioned_atom")
[docs]
def operations(self, domain, **kwds):
return self._operations(domain, self, **kwds)
@property
def is_supported(self):
return True
[docs]
class wrapper(base):
__slots__ = ("_raw_pkg", "_domain")
[docs]
def operations(self, domain, **kwds):
return self._raw_pkg._operations(domain, self, **kwds)
def __init__(self, raw_pkg):
object.__setattr__(self, "_raw_pkg", raw_pkg)
def __eq__(self, other):
if isinstance(other, wrapper):
return self._raw_pkg == other._raw_pkg
try:
return self._raw_pkg == other
except TypeError:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
if isinstance(other, wrapper):
return self._raw_pkg < other._raw_pkg
return self._raw_pkg < other
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
def __gt__(self, other):
if isinstance(other, wrapper):
return self._raw_pkg > other._raw_pkg
return self._raw_pkg > other
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
__getattr__ = klass.GetAttrProxy("_raw_pkg")
__dir__ = klass.DirProxy("_raw_pkg")
_get_attr = klass.alias_attr("_raw_pkg._get_attr")
built = klass.alias_attr("_raw_pkg.built")
versioned_atom = klass.alias_attr("_raw_pkg.versioned_atom")
unversioned_atom = klass.alias_attr("_raw_pkg.unversioned_atom")
is_supported = klass.alias_attr("_raw_pkg.is_supported")
def __hash__(self):
return hash(self._raw_pkg)
[docs]
def dynamic_getattr_dict(self, attr):
functor = self._get_attr.get(attr)
if functor is None:
raise AttributeError(self, attr)
try:
val = functor(self)
object.__setattr__(self, attr, val)
return val
except errors.MetadataException as e:
if e.attr == attr:
raise
raise errors.MetadataException(self, attr, e.error, e.verbose) from e
except (errors.PackageError, UnicodeDecodeError) as e:
raise errors.MetadataException(self, attr, str(e)) from e
except PermissionError as e:
raise base_errors.PermissionDenied(self.path, write=False) from e
[docs]
class DynamicGetattrSetter(type):
"""Metaclass utilizing __getattr__ to JIT generate attributes and store them.
Consider `snakeoil.klass.jit_attr` for comparison; that pseudo property
will invoke a functor and store the result, but every subsequent access- still
pays overhead of passing through the redirects.
This metaclass lacks that overhead; via hooking __getattr__, this generates
the requested attribute, stores it on the instance, and returns it. All
future access of that attribute go through the fast path cpy access ways.
This optimization in the early days of pkgcore had drastic impact; in modern
times python has improved. There still is gain, but the implementation complexity
may warrant phasing this out in favor of `snakeoil.klas.jit_attr` alternatives.
"""
[docs]
class register:
"""Decorator used to mark a function as an attribute loader."""
__slots__ = ("functor",)
def __init__(self, functor):
self.functor = functor
def __new__(cls, name, bases, class_dict):
new_functions = {
attr: class_dict.pop(attr).functor
for attr, thing in list(class_dict.items())
if isinstance(thing, cls.register)
}
existing = {}
for base in bases:
existing.update(getattr(base, "_get_attr", {}))
slots = class_dict.get("__slots__", None)
if slots is not None:
# only add slots for new attr's; assume the layer above already slotted
# if this layer is setting slots.
class_dict["__slots__"] = tuple(
sequences.iter_stable_unique(
itertools.chain(slots, set(new_functions).difference(existing))
)
)
d = (
existing
if class_dict.pop("__DynamicGetattrSetter_auto_inherit__", True)
else {}
)
d.update(new_functions)
d.update(class_dict.pop("_get_attr", {}))
class_dict["_get_attr"] = d
class_dict.setdefault("__getattr__", dynamic_getattr_dict)
return type.__new__(cls, name, bases, class_dict)