Skip to content

Instantly share code, notes, and snippets.

@asvetlov
Created October 22, 2014 17:56
Show Gist options
  • Select an option

  • Save asvetlov/1a24f6f4a63c6c2f1932 to your computer and use it in GitHub Desktop.

Select an option

Save asvetlov/1a24f6f4a63c6c2f1932 to your computer and use it in GitHub Desktop.

Revisions

  1. asvetlov created this gist Oct 22, 2014.
    155 changes: 155 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,155 @@
    from inspect import getattr_static


    class dep:

    name = None

    def __init__(self, type):
    self.type = type

    def __get__(self, instance, owner):
    if self.name is None:
    raise RuntimeError("component was not initialized, use @component "
    "decorator for {!r}".format(owner))
    if instance is None:
    return self
    else:
    try:
    return instance.__dict__[self.name]
    except KeyError:
    raise AttributeError("dep {!r} [{}] is not initialized".format(
    self.name, self.type))

    def __set__(self, instance, value):
    raise TypeError("Cannot overwrite dependency")

    def __delete__(self, instance):
    raise TypeError("Cannot delete dependency")

    def __repr__(self):
    return '<Dep {!r} [{}]>'.format(self.name, self.type)


    def component(cls):
    deps = []
    for k in dir(cls):
    v = getattr_static(cls, k)
    if isinstance(v, dep):
    # assert v.name is None, v.name
    v.name = k
    deps.append(v)
    cls.__di_deps__ = tuple(deps)
    return cls


    class DI:
    """Base class for components with dependencies.

    Usage example:

    >>> @component
    ... class A:
    ... a = dep(int)
    ... b = dep(str)
    ...
    >>> DI(A).deps
    (<Dep 'a' [<class 'int'>]>, <Dep 'b' [<class 'str'>]>)
    >>> a = A()
    >>> DI(a).setup(a=1, b='val')
    >>> a.a
    1
    >>> a.b
    'val'
    """

    def __init__(self, inst):
    self._inst = inst
    self._deps = getattr(inst, '__di_deps__', None)
    if self._deps is None:
    raise TypeError("Argument is not component")

    @property
    def deps(self):
    """Dependencies for component"""
    return self._deps

    def setup(self, **kwargs):
    """Initializes self dependencies.

    **kwargs -- list of name->value pairs for dependencies to set up.
    name is a name of dependency and value is a assigned value.

    >>> @component
    >>> class A:
    ... a = dep(int)
    ...
    >>> a = A()
    >>> DI(a).setup(a=1)
    >>> a.a
    1

    Raises TypeError if value has a different type
    than has been specified in dep(type) call.
    Raises AttributeError if name doesn't point to any dependency.
    """
    marker = object()
    to_set = {}
    for d in self._deps:
    v = kwargs.pop(d.name, marker)
    if v is not marker:
    if not isinstance(v, d.type):
    raise TypeError("Invalid type for {}, got {}"
    .format(d, type(v)))
    else:
    to_set[d.name] = v
    if kwargs:
    raise AttributeError("dependencies {} are not found"
    .format(', '.join(sorted(kwargs))))

    # Assign new values only after all checks passed
    self._inst.__dict__.update(to_set)

    def inject(self, component, *, ready=True):
    """Injects self dependencies into component.

    component -- a component for injecting self dependencies into.
    ready -- do self.ready() check before injecting (yes by default).
    """
    cdi = DI(component)
    self_dct = self._inst.__dict__
    cdi_dct = cdi._inst.__dict__
    if ready:
    self.ready()
    marker = object()
    vals = {d.name: self_dct[d.name] for d in self._deps
    if d.name in self_dct}
    to_set = {}
    for d in cdi._deps:
    name = d.name
    selfval = vals.get(name, marker)
    if selfval is marker:
    continue # nothing to assign
    otherval = cdi_dct.get(name, marker)
    if otherval is marker:
    if not isinstance(selfval, d.type):
    raise TypeError("Invalid type for {}: {!r}"
    .format(d, selfval))
    else:
    to_set[d.name] = selfval
    continue
    else:
    if selfval is not otherval:
    raise TypeError("Overrriding {} with {!r}"
    .format(d, selfval))
    for k, v in to_set.items():
    cdi_dct[k] = v
    return component

    def ready(self):
    """Check self for all dependencies has been initialized."""
    missed = {d.name for d in self._deps
    if d.name not in self._inst.__dict__}
    if missed:
    raise ValueError("Not-initialized attributes {}"
    .format(', '.join(sorted(missed))))