Skip to content

Commit

Permalink
add Magic mixins
Browse files Browse the repository at this point in the history
  • Loading branch information
ypankovych committed May 26, 2021
1 parent 6576a37 commit 8eeb1a8
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 2 deletions.
4 changes: 3 additions & 1 deletion docs/source/magic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ Pankoff's magic

.. autofunction:: pankoff.magic.autoinit(klass, verbose=False)

.. autoclass:: pankoff.magic.Alias
.. autoclass:: pankoff.magic.Alias

.. autoclass:: pankoff.magic.MagicMixin
61 changes: 61 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,64 @@ It is possible to make object factory based on the same Container class.
As you can see, ``young_person`` and ``old_person`` acting like completely different things, but in fact they're not.

Also, you can access underlying ``extra`` structure by doing ``yaroslav._extra``, which returns ``MappingProxyType`` view.

.. _Magic mixins:

Magic mixins
------------

Lets say you want to create a mixin, normally you'd do:

.. code-block:: python
class HelloMixin:
def say(self):
value = super().say()
return f"My name is {value} !!!"
class Hello:
def say(self):
return self.name
class Person(HelloMixin, Hello):
def __init__(self, name):
self.name = name
person = Person("Yaroslav")
print(person.say()) # hello, my name is Yaroslav
As you can see, we're using ``super()`` here. Magic mixins allows you to avoid that, e.g:

.. code-block:: python
from pankoff.magic import MagicMixin
class HelloMixin(MagicMixin):
def say(self, value):
return f"My name is {value} !!!"
class Hello:
def say(self):
return self.name
class Person(HelloMixin, Hello):
def __init__(self, name):
self.name = name
person = Person("Yaroslav")
print(person.say()) # hello, my name is Yaroslav
So the idea is, when you have same method names in both mixin and its parent, mixin will consume parents' value implicitly.

In the example above, ``value`` parameter for ``HelloMixin`` is the result ``say`` method on ``Hello`` class.

Note that it'll chain through all the mixins in MRO.
45 changes: 45 additions & 0 deletions pankoff/magic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import inspect
import textwrap

from pankoff.base import BaseValidator
from pankoff.validators import UNSET, LazyLoad

Expand Down Expand Up @@ -103,6 +106,48 @@ def inner(cls):
return inner


def _replace_method(method):
ns = {
"UNSET": UNSET,
"method": method
}
mixin_method_template = textwrap.dedent("""
def mixed_method_{method_name}(self, *args, **kwargs):
parent = eval(f"getattr(super(klass, self), '{method_name}', UNSET)")
if parent is UNSET:
return method(self, *args, **kwargs)
else:
return method(self, parent(*args, **kwargs))
""")
exec(mixin_method_template.format(method_name=method.__name__), ns)
return ns[f"mixed_method_{method.__name__}"], ns


class MixinMeta(type):

def __new__(mcs, name, bases, namespace):
nss = []
if all(issubclass(base, MagicMixin) for base in bases):
for k, v in namespace.items():
if inspect.isfunction(v) or isinstance(v, classmethod):
new_f, ns = _replace_method(v)
namespace[k] = new_f
nss.append(ns)
klass = super().__new__(mcs, name, bases, namespace)
for ns in nss:
ns["klass"] = klass
return klass


class MagicMixin(metaclass=MixinMeta):
"""
Create ``MagicMixin`` by inheriting from it.
See examples: :ref:`magic mixins`
"""
pass


class Alias:
"""
Create and alias for your fields.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
from distutils.core import setup

version = "15.5"
version = "15.6"
setup(
name='pankoff',
packages=['pankoff'],
Expand Down

0 comments on commit 8eeb1a8

Please sign in to comment.