dyndesign


Namedyndesign JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/amarula/dyndesign
SummaryToolset for Dynamic Design in Python.
upload_time2023-09-22 14:40:23
maintainerPatrizio Gelosi
docs_urlNone
authorPatrizio Gelosi
requires_python>=3.8,<4.0
licenseMIT
keywords design dynamic decorator inheritance
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            DynDesign
=========

|Build Status| |PyPi Version Status| |Python Version Status| |License|

A set of tools for Dynamic Design in Python.


Documentation
-------------

DynDesign's full documentation can be found at
https://dyndesign.readthedocs.io/en/latest/


Install
-------

Dyndesign is on the Python Package Index (PyPI):

::

    pip install dyndesign


Overview
--------
Dyndesign is a toolkit that gives developers the ultimate flexibility in
dynamically designing class structures.

Here is an overview of DynDesign's tools.

* Dynamically build a class by adding parent and component classes to a Base class,
  based on selected Building Options:

.. code:: python

    from dyndesign import buildclass, dynconfig

    class Parent:
        ...

    class Component:
        ...

    @dynconfig({
        "OptionA": ClassConfig(inherit_from=Parent),
        "OptionB": ClassConfig(component_attr="comp", component_class=Component),
    })
    class Base:
        ...

    BuiltClass = buildclass(Base, OptionA=True, OptionB=True)
    b = BuiltClass()
    b.method_of_parent()
    b.comp.method_of_component()

* Dynamically add parent classes to a class:

.. code:: python

    from dyndesign import DynInheritance

    class Parent1:
        ...

    class Child(DynInheritance):
        ...

    Child.dynparents_add(Parent1)
    c = Child()
    c.method_of_parent1()

* Merge two or more classes:

.. code:: python

    from dyndesign import mergeclasses

    class Base:
        ...

    class Ext1:
        ...

    MergedClass = mergeclasses(Base, Ext1)
    m = MergedClass()
    m.method_of_Ext1()

* Decorate a method with one or more instance methods loaded at runtime:

.. code:: python

    from dyndesign import decoratewith

    @decoratewith("decorator_1", "component.decorator_2", ...)
    def decorated_method(self, ...):
        ...

* Safely invoke functions or methods from a ``safezone`` context manager or by
  using the ``safeinvoke`` API:

.. code:: python

    from dyndesign import safezone, safeinvoke

    with safezone():
        ...
        function_possibly_non_existent()

    ...

    def method(self):
        safeinvoke("method_possibly_non_existent", self)

* Create and destroy Singleton classes:

.. code:: python

    from dyndesign import SingletonMeta

    class Singleton(metaclass=SingletonMeta):
        ...

    singleton_instance = Singleton(...)
    same_singleton_instance = Singleton()
    Singleton().destroy_singleton()
    new_singleton_instance = Singleton(...)

* Import classes dynamically using the path:

.. code:: python

    from dyndesign import importclass

    ImportedClass = importclass("directory.module.class_name")


Class Builder
-------------

Class Builder is a powerful new tool from DynDesign that makes it easy to build
classes by configuring existing classes with selected options.

Building classes involves incorporating one or more Class Dependencies, including
**parent classes** and **component classes**. This can be achieved using two
essential tools: the ``dynconfig`` decorator, which allows the base class to be
configured with potential dependencies, and the ``buildclass`` function, which
builds new classes by seamlessly integrating selected class dependencies using a
specified set of building options.

Below is an example of building a class that optionally inherits from classes A
and B.

.. code:: python

    from dyndesign import buildclass, dynconfig, ClassConfig

    class A:
        def __init__(self):
            print("Inheriting from `A`")

    class B:
        def __init__(self):
            print("Inheriting from `B`")


    @dynconfig({
        "OptionA": ClassConfig(inherit_from=A),
        "OptionB": ClassConfig(inherit_from=B),
    })
    class Base:
        ...


    Built = buildclass(Base, OptionA=True)
    Built()
    # Inheriting from `A`

    Built = buildclass(Base, OptionB=True)
    Built()
    # Inheriting from `B`

Classes can be configured to enable the injection of component classes into
specific methods (or into the default ``__init__`` method).

.. code:: python

    from dyndesign import buildclass, dynconfig, ClassConfig

    class A:
        def whoami(self):
            print("Using component `A`")

    class Default:
        def whoami(self):
            print("Using component `Default`")

    class Configurator:
        OptionA = ClassConfig(
            component_class=A,
            component_attr="comp",
            default_class=Default
        )

    @dynconfig(Configurator)
    class Base:
        def __init__(self):
            self.comp.whoami()


    Built = buildclass(Base, OptionA=True)
    Built()
    # Using component `A`

    Built = buildclass(Base, OptionA=False)
    Built()
    # Using component `Default`

Another important point demonstrated in the example is that class configuration
can be encapsulated in a Configurator class. This helps to **separate** the code
that is responsible for **class configuration from the core logic** of the
classes.

Dynamic Inheritance
-------------------

With Dynamic Inheritance, it becomes possible to dynamically modify the
superclass set of classes that inherit from special class ``DynInheritance``. This
allows the addition of parent classes to those classes, and the modification is
also instantly reflected in all their instances.

.. code:: python

    from dyndesign import DynInheritance

    class Parent:
        def m1(self):
            print("Method `m1` from `Parent`")

    class Child(DynInheritance):
        def __init__(self):
            print("Constructor of `Child`")

    child_instance = Child()

    # Constructor of `Child`

    Child.dynparents_add(Parent)
    child_instance.m1()

    # Method `m1` from `Parent`

When the special class ``DynInheritanceLockedInstances`` is utilized instead of
``DynInheritance``, the superclass set is locked within each class instance,
meaning that it remains unchanged even when there are modifications to the
class's superclasses.

.. code:: python

    class Parent:
        def __init__(self):
            print("Constructor of `Parent`")

        def mtd(self):
            print("Method `mtd` of `Parent`")

    class Child(DynInheritanceLockedInstances):
        def __init__(self):
            super(DynInheritanceLockedInstances, self).__init__()
            print("Constructor of `Child`")

    orphan_child = Child()

    # Constructor of `Child`

    Child.dynparents_add(Parent)
    child_with_parent = Child()

    # Constructor of `Parent`
    # Constructor of `Child`

    child_with_parent.mtd()

    # Method `mtd` of `Parent`

    orphan_child.mtd()

    # AttributeError: 'Child' object has no attribute 'mtd'

Class Merging
-------------

Dyndesign provides API ``mergeclasses`` to merge two or more classes as if they
were dictionaries. As a result, the newly created class has the same properties
from both its base class and any added extensions. If two or more classes have
the same attributes/methods, the attributes/methods from the rightmost classes
(in the order in which the classes are passed to ``mergeclasses``) overload the
ones from the leftmost classes, similarly to what happens when merging
dictionaries.

.. code:: python

    from dyndesign import mergeclasses

    class Base:
        def __init__(self, init_value):
            self.param = init_value

        def m1(self):
            print(f"Method `m1` of class `Base`, and {self.param=}")

        def m2(self):
            print(f"Method `m2` of class `Base`")

    class Ext:
        def m1(self):
            print(f"Method `m1` of class `Ext`, and {self.param=}")

    MergedClass = mergeclasses(Base, Ext)
    merged_instance = MergedClass("INITIAL VALUE")
    merged_instance.m1()
    merged_instance.m2()

    # Method `m1` of class `Ext`, and self.param='INITIAL VALUE'
    # Method `m2` of class `Base`


When a merged class is instantiated with arguments, the constructor of each
merging class is invoked, since constructors are excluded from being overloaded.
Also, arguments passed to each constructor are adaptively filtered based on the
constructor signature so that each constructor takes just the arguments it
requires, and no exception is raised for exceeding arguments passed:

.. code:: python

    class A:
        def __init__(self):
            print("No argument passed to class `A`")

    class B:
        def __init__(self, a):
            print(f"Argument {a=} passed to class `B`")

    class C:
        def __init__(self, a, b, kw1=None):
            print(f"Argument {a=}, {b=} and {kw1=} passed to class `C`")

    class D:
        def __init__(self, kw2=None):
            print(f"Argument {kw2=} passed to class `D`")

    MergedClass = mergeclasses(A, B, C, D)
    MergedClass("Alpha", "Beta", kw1="kwarg #1", kw2="kwarg #2")

    # No argument passed to class `A`
    # Argument a='Alpha' passed to class `B`
    # Argument a='Alpha', b='Beta' and kw1='kwarg #1' passed to class `C`
    # Argument kw2='kwarg #2' passed to class `D`

On the other hand, if any required positional argument is missing, an exception
is raised. If ``MergedClass`` of the above example is initialized with no
parameters, and exception is raised when the constructor of class ``B`` is
called:

.. code:: python

    ...
    MergedClass()

    # ...
    # TypeError: B.__init__() missing 1 required positional argument: 'a'

So as to have constructor instances with missing positional arguments silently
skipped, ``strict_merged_args`` can be set to False in ``mergeclasses``. In the
above example, constructors of class ``B`` and ``C`` are skipped:

.. code:: python

    ...
    MergedClass = mergeclasses(A, B, C, D, strict_merged_args=False)
    MergedClass()

    # No argument passed to class `A`
    # Argument kw2=None passed to class `D`


It is also possible to extend the same behavior of the constructor ``__init__``
(i.e., all the methods from all the merged classes are invoked rather than being
overloaded by the same name method from the rightmost class) to other methods. A
list of method names whose instances must be all invoked can be specified in
the ``invoke_all`` argument of ``mergeclasses``. Adaptive filtering of the
arguments of the method instances is performed as well.

.. code:: python

    class E:
        def method(self):
            print("No argument passed to `method` of class `E`")

    class F:
        def method(self, a):
            print(f"Argument {a=} passed to `method` of class `F`")

    MergedClass = mergeclasses(E, F, invoke_all=["method"])
    MergedClass().method("Alpha")

    # No argument passed to `method` of class `E`
    # Argument a='Alpha' passed to `method` of class `F`


Dynamic Decorators
------------------

Meta decorator ``decoratewith`` can be used to decorate a class method with one
or more chained dynamic decorators, regardless whether they statically exist
or not. Additionally, the syntax of the dynamic decorators aims to get rid of
the boilerplate for wrapping and returning the decorator code, leaving just the
wrapper's code. For example, dynamic decorators can be used to decorate a method
of a base class with a method of an extension class:

.. code:: python

    from dyndesign import decoratewith, mergeclasses

    class Base:
        @decoratewith("decorator")
        def m(self):
            print(f"Method `m` of class `Base`")

    class Ext:
        def decorator(self, func):
            print("Beginning of method decoration from Ext.")
            func(self)
            print("End of method decoration from Ext.")

    merged = mergeclasses(Base, Ext)()
    merged.m()

    # Beginning of method decoration from Ext.
    # Method `m` of class `Base`
    # End of method decoration from Ext.

If a decorator name is passed in the ``invoke_all`` argument of
``mergeclasses``, then multiple decorator instances with the same name from
different extension classes may be used in chain:

.. code:: python

    class Ext2:
        def decorator(self, func):
            print("Beginning of method decoration from Ext2.")
            func(self)
            print("End of method decoration from Ext2.")

    merged = mergeclasses(Base, Ext, Ext2, invoke_all=["decorator"])()
    merged.m()

    # Beginning of method decoration from Ext.
    # Beginning of method decoration from Ext2.
    # Method `m` of class `Base`
    # End of method decoration from Ext2.
    # End of method decoration from Ext.


Arguments of ``decoratewith`` are loaded at runtime as properties of the
variable 'self': a dynamic decorator can be, for example, a method of a
component class. In case of dynamic decoration from a sub-instance of 'self',
the instance object of the decorated method is passed to the decorator as the
argument ``decorated_self``. If a dynamic decorator is not found at runtime
(e.g., because it is a method of an optional class that has not been merged),
then the code execution proceeds normally, as shown below with the decorator
``non_existent_decorator``:

.. code:: python

    class Base:
        def __init__(self):
            self.comp = Component()

        @decoratewith("comp.decorator1", "comp.decorator2", "non_existent_decorator")
        def m(self):
            print("Method `m` of class `Base`")

    class Component:
        def __init__(self):
            self.value = "Initial"

        def decorator1(self, func, decorated_self):
            print(f"Beginning of method decoration #1 ({self.value=})")
            self.value = "Processed"
            func(decorated_self)
            print("End of method decoration #1")

        def decorator2(self, func, decorated_self):
            print(f"Beginning of method decoration #2 ({self.value=})")
            func(decorated_self)
            print("End of method decoration #2")

    base = Base()
    base.m()

    # Beginning of method decoration #1 (self.value='Initial')
    # Beginning of method decoration #2 (self.value='Processed')
    # Method `m` of class `Base`
    # End of method decoration #2
    # End of method decoration #1


Safezone Context Manager
------------------------

Any function or method that may or may not exist at runtime (e.g., methods of
merged classes) can be invoked from Context Manager ``safezone`` in order to
suppress the possible exceptions raised if the function or method is not found
at runtime. Optionally, a fallback function/method can be also passed. If no
function name(s) is passed as argument of ``safezone``, then each function in
the safe zone's code is protected; if any function name(s) is passed, the
protection is restricted to the functions having that/those name(s). For
example, ``safezone`` can be used to safely call functions that may or may not
exist at runtime:

.. code:: python

    from dyndesign import safezone

    def fallback():
        print("Fallback function")

    def function_a():
        print("Function `a`")

    with safezone(fallback=fallback):
        function_a()
        non_existent_function()

    # Function `a`
    # Fallback function


A further example shows that ``safezone`` can be used to safely invoke methods
of classes that may or may not be merged with other classes:

.. code:: python

    class Base:
        def fallback(self):
            print("Fallback method")

        def m(self, class_desc):
            print(f"Method `m` of {class_desc}")
            with safezone("optional_method", fallback=self.fallback):
                self.optional_method()

    class ExtOptional:
        def optional_method(self):
            print("Optional method from class `ExtOptional`")

    merged = mergeclasses(Base, ExtOptional)()
    merged.m("merged class")
    base = Base()
    base.m("class `Base` standalone")

    # Method `m` of merged class
    # Optional method from class `ExtOptional`
    # Method `m` of class `Base` standalone
    # Fallback method


Invoking methods safely
-----------------------

As an alternative to ``safezone`` context manager, ``safeinvoke`` API can be
used to safely invoke methods that may or may not exist at runtime. To this end,
method ``m`` of class ``Base`` of the example above can be replaced as follows:

.. code:: python

    from dyndesign import safeinvoke

    ...

        def m(self, class_desc):
            print(f"Method `m` of {class_desc}")
            safeinvoke("optional_method", self, fallback=self.fallback)


Singleton classes
-----------------

Singleton classes can be swiftly created with `SingletonMeta` metaclass and then
destroyed with `destroy_singleton`:

.. code:: python

    from dyndesign import SingletonMeta

    class Singleton(metaclass=SingletonMeta):
        def __init__(self, instance_id = None):
            if instance_id:
                self.instance_id = instance_id
            print(f"Created a {instance_id} instance of `Singleton`")

        def where_points(self, object_name):
            print(f"Object `{object_name}` points to the {self.instance_id} instance")

    s_A = Singleton("first")
    s_A.where_points("s_A")

    # Created a first instance of `Singleton`
    # Object `s_A` points to the first instance

    s_B = Singleton("second")
    s_B.where_points("s_B")

    # Object `s_B` points to the first instance

    Singleton().destroy_singleton()
    s_C = Singleton("second")
    s_C.where_points("s_C")

    # Created a second instance of `Singleton`
    # Object `s_C` points to the second instance

The class method ``destroy`` of SingletonMeta can be invoked to destroy all the
Singleton classes at once. As a further alternative to the instance call
``destroy_singleton``, the names of the Singleton classes to destroy can be
passed to the class method ``destroy``:

.. code:: python

    Singleton().destroy_singleton() # Destroy only `Singleton`
    SingletonMeta.destroy() # Destroy all the singleton classes
    SingletonMeta.destroy('Singleton1', 'Singleton2', 'Singleton3') # Destroy selectively


Importing classes dynamically
-----------------------------

Classes can be imported dynamically using the package/class names or the path in
dot-notation as shown below:

.. code:: python

    from dyndesign import importclass

    ClassA = importclass('package_A', 'ClassA')
    ClassB = importclass('directory_B.package_B.ClassB')


Running tests
-------------

To run the tests using your default python interpreter:

::

    pip install -U pytest
    python -m pytest test


.. |Build Status| image:: https://github.com/amarula/dyndesign/actions/workflows/python-app.yml/badge.svg
    :target: https://github.com/amarula/dyndesign/actions
.. |Python Version Status| image:: https://img.shields.io/badge/python-3.8_3.9_3.10_3.11-blue.svg
    :target: https://github.com/amarula/dyndesign/actions
.. |PyPi Version Status| image:: https://badge.fury.io/py/dyndesign.svg
    :target: https://badge.fury.io/py/dyndesign
.. |License| image:: https://img.shields.io/badge/License-MIT-yellow.svg
    :target: https://opensource.org/licenses/MIT


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/amarula/dyndesign",
    "name": "dyndesign",
    "maintainer": "Patrizio Gelosi",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "patrizio.gelosi@amarulasolutions.com",
    "keywords": "design,dynamic,decorator,inheritance",
    "author": "Patrizio Gelosi",
    "author_email": "patrizio.gelosi@amarulasolutions.com",
    "download_url": "https://files.pythonhosted.org/packages/85/1f/1601c979ab61836d352b1f1e285d2e0370d81cea9ec69af10462d538ab28/dyndesign-1.1.0.tar.gz",
    "platform": null,
    "description": "DynDesign\n=========\n\n|Build Status| |PyPi Version Status| |Python Version Status| |License|\n\nA set of tools for Dynamic Design in Python.\n\n\nDocumentation\n-------------\n\nDynDesign's full documentation can be found at\nhttps://dyndesign.readthedocs.io/en/latest/\n\n\nInstall\n-------\n\nDyndesign is on the Python Package Index (PyPI):\n\n::\n\n    pip install dyndesign\n\n\nOverview\n--------\nDyndesign is a toolkit that gives developers the ultimate flexibility in\ndynamically designing class structures.\n\nHere is an overview of DynDesign's tools.\n\n* Dynamically build a class by adding parent and component classes to a Base class,\n  based on selected Building Options:\n\n.. code:: python\n\n    from dyndesign import buildclass, dynconfig\n\n    class Parent:\n        ...\n\n    class Component:\n        ...\n\n    @dynconfig({\n        \"OptionA\": ClassConfig(inherit_from=Parent),\n        \"OptionB\": ClassConfig(component_attr=\"comp\", component_class=Component),\n    })\n    class Base:\n        ...\n\n    BuiltClass = buildclass(Base, OptionA=True, OptionB=True)\n    b = BuiltClass()\n    b.method_of_parent()\n    b.comp.method_of_component()\n\n* Dynamically add parent classes to a class:\n\n.. code:: python\n\n    from dyndesign import DynInheritance\n\n    class Parent1:\n        ...\n\n    class Child(DynInheritance):\n        ...\n\n    Child.dynparents_add(Parent1)\n    c = Child()\n    c.method_of_parent1()\n\n* Merge two or more classes:\n\n.. code:: python\n\n    from dyndesign import mergeclasses\n\n    class Base:\n        ...\n\n    class Ext1:\n        ...\n\n    MergedClass = mergeclasses(Base, Ext1)\n    m = MergedClass()\n    m.method_of_Ext1()\n\n* Decorate a method with one or more instance methods loaded at runtime:\n\n.. code:: python\n\n    from dyndesign import decoratewith\n\n    @decoratewith(\"decorator_1\", \"component.decorator_2\", ...)\n    def decorated_method(self, ...):\n        ...\n\n* Safely invoke functions or methods from a ``safezone`` context manager or by\n  using the ``safeinvoke`` API:\n\n.. code:: python\n\n    from dyndesign import safezone, safeinvoke\n\n    with safezone():\n        ...\n        function_possibly_non_existent()\n\n    ...\n\n    def method(self):\n        safeinvoke(\"method_possibly_non_existent\", self)\n\n* Create and destroy Singleton classes:\n\n.. code:: python\n\n    from dyndesign import SingletonMeta\n\n    class Singleton(metaclass=SingletonMeta):\n        ...\n\n    singleton_instance = Singleton(...)\n    same_singleton_instance = Singleton()\n    Singleton().destroy_singleton()\n    new_singleton_instance = Singleton(...)\n\n* Import classes dynamically using the path:\n\n.. code:: python\n\n    from dyndesign import importclass\n\n    ImportedClass = importclass(\"directory.module.class_name\")\n\n\nClass Builder\n-------------\n\nClass Builder is a powerful new tool from DynDesign that makes it easy to build\nclasses by configuring existing classes with selected options.\n\nBuilding classes involves incorporating one or more Class Dependencies, including\n**parent classes** and **component classes**. This can be achieved using two\nessential tools: the ``dynconfig`` decorator, which allows the base class to be\nconfigured with potential dependencies, and the ``buildclass`` function, which\nbuilds new classes by seamlessly integrating selected class dependencies using a\nspecified set of building options.\n\nBelow is an example of building a class that optionally inherits from classes A\nand B.\n\n.. code:: python\n\n    from dyndesign import buildclass, dynconfig, ClassConfig\n\n    class A:\n        def __init__(self):\n            print(\"Inheriting from `A`\")\n\n    class B:\n        def __init__(self):\n            print(\"Inheriting from `B`\")\n\n\n    @dynconfig({\n        \"OptionA\": ClassConfig(inherit_from=A),\n        \"OptionB\": ClassConfig(inherit_from=B),\n    })\n    class Base:\n        ...\n\n\n    Built = buildclass(Base, OptionA=True)\n    Built()\n    # Inheriting from `A`\n\n    Built = buildclass(Base, OptionB=True)\n    Built()\n    # Inheriting from `B`\n\nClasses can be configured to enable the injection of component classes into\nspecific methods (or into the default ``__init__`` method).\n\n.. code:: python\n\n    from dyndesign import buildclass, dynconfig, ClassConfig\n\n    class A:\n        def whoami(self):\n            print(\"Using component `A`\")\n\n    class Default:\n        def whoami(self):\n            print(\"Using component `Default`\")\n\n    class Configurator:\n        OptionA = ClassConfig(\n            component_class=A,\n            component_attr=\"comp\",\n            default_class=Default\n        )\n\n    @dynconfig(Configurator)\n    class Base:\n        def __init__(self):\n            self.comp.whoami()\n\n\n    Built = buildclass(Base, OptionA=True)\n    Built()\n    # Using component `A`\n\n    Built = buildclass(Base, OptionA=False)\n    Built()\n    # Using component `Default`\n\nAnother important point demonstrated in the example is that class configuration\ncan be encapsulated in a Configurator class. This helps to **separate** the code\nthat is responsible for **class configuration from the core logic** of the\nclasses.\n\nDynamic Inheritance\n-------------------\n\nWith Dynamic Inheritance, it becomes possible to dynamically modify the\nsuperclass set of classes that inherit from special class ``DynInheritance``. This\nallows the addition of parent classes to those classes, and the modification is\nalso instantly reflected in all their instances.\n\n.. code:: python\n\n    from dyndesign import DynInheritance\n\n    class Parent:\n        def m1(self):\n            print(\"Method `m1` from `Parent`\")\n\n    class Child(DynInheritance):\n        def __init__(self):\n            print(\"Constructor of `Child`\")\n\n    child_instance = Child()\n\n    # Constructor of `Child`\n\n    Child.dynparents_add(Parent)\n    child_instance.m1()\n\n    # Method `m1` from `Parent`\n\nWhen the special class ``DynInheritanceLockedInstances`` is utilized instead of\n``DynInheritance``, the superclass set is locked within each class instance,\nmeaning that it remains unchanged even when there are modifications to the\nclass's superclasses.\n\n.. code:: python\n\n    class Parent:\n        def __init__(self):\n            print(\"Constructor of `Parent`\")\n\n        def mtd(self):\n            print(\"Method `mtd` of `Parent`\")\n\n    class Child(DynInheritanceLockedInstances):\n        def __init__(self):\n            super(DynInheritanceLockedInstances, self).__init__()\n            print(\"Constructor of `Child`\")\n\n    orphan_child = Child()\n\n    # Constructor of `Child`\n\n    Child.dynparents_add(Parent)\n    child_with_parent = Child()\n\n    # Constructor of `Parent`\n    # Constructor of `Child`\n\n    child_with_parent.mtd()\n\n    # Method `mtd` of `Parent`\n\n    orphan_child.mtd()\n\n    # AttributeError: 'Child' object has no attribute 'mtd'\n\nClass Merging\n-------------\n\nDyndesign provides API ``mergeclasses`` to merge two or more classes as if they\nwere dictionaries. As a result, the newly created class has the same properties\nfrom both its base class and any added extensions. If two or more classes have\nthe same attributes/methods, the attributes/methods from the rightmost classes\n(in the order in which the classes are passed to ``mergeclasses``) overload the\nones from the leftmost classes, similarly to what happens when merging\ndictionaries.\n\n.. code:: python\n\n    from dyndesign import mergeclasses\n\n    class Base:\n        def __init__(self, init_value):\n            self.param = init_value\n\n        def m1(self):\n            print(f\"Method `m1` of class `Base`, and {self.param=}\")\n\n        def m2(self):\n            print(f\"Method `m2` of class `Base`\")\n\n    class Ext:\n        def m1(self):\n            print(f\"Method `m1` of class `Ext`, and {self.param=}\")\n\n    MergedClass = mergeclasses(Base, Ext)\n    merged_instance = MergedClass(\"INITIAL VALUE\")\n    merged_instance.m1()\n    merged_instance.m2()\n\n    # Method `m1` of class `Ext`, and self.param='INITIAL VALUE'\n    # Method `m2` of class `Base`\n\n\nWhen a merged class is instantiated with arguments, the constructor of each\nmerging class is invoked, since constructors are excluded from being overloaded.\nAlso, arguments passed to each constructor are adaptively filtered based on the\nconstructor signature so that each constructor takes just the arguments it\nrequires, and no exception is raised for exceeding arguments passed:\n\n.. code:: python\n\n    class A:\n        def __init__(self):\n            print(\"No argument passed to class `A`\")\n\n    class B:\n        def __init__(self, a):\n            print(f\"Argument {a=} passed to class `B`\")\n\n    class C:\n        def __init__(self, a, b, kw1=None):\n            print(f\"Argument {a=}, {b=} and {kw1=} passed to class `C`\")\n\n    class D:\n        def __init__(self, kw2=None):\n            print(f\"Argument {kw2=} passed to class `D`\")\n\n    MergedClass = mergeclasses(A, B, C, D)\n    MergedClass(\"Alpha\", \"Beta\", kw1=\"kwarg #1\", kw2=\"kwarg #2\")\n\n    # No argument passed to class `A`\n    # Argument a='Alpha' passed to class `B`\n    # Argument a='Alpha', b='Beta' and kw1='kwarg #1' passed to class `C`\n    # Argument kw2='kwarg #2' passed to class `D`\n\nOn the other hand, if any required positional argument is missing, an exception\nis raised. If ``MergedClass`` of the above example is initialized with no\nparameters, and exception is raised when the constructor of class ``B`` is\ncalled:\n\n.. code:: python\n\n    ...\n    MergedClass()\n\n    # ...\n    # TypeError: B.__init__() missing 1 required positional argument: 'a'\n\nSo as to have constructor instances with missing positional arguments silently\nskipped, ``strict_merged_args`` can be set to False in ``mergeclasses``. In the\nabove example, constructors of class ``B`` and ``C`` are skipped:\n\n.. code:: python\n\n    ...\n    MergedClass = mergeclasses(A, B, C, D, strict_merged_args=False)\n    MergedClass()\n\n    # No argument passed to class `A`\n    # Argument kw2=None passed to class `D`\n\n\nIt is also possible to extend the same behavior of the constructor ``__init__``\n(i.e., all the methods from all the merged classes are invoked rather than being\noverloaded by the same name method from the rightmost class) to other methods. A\nlist of method names whose instances must be all invoked can be specified in\nthe ``invoke_all`` argument of ``mergeclasses``. Adaptive filtering of the\narguments of the method instances is performed as well.\n\n.. code:: python\n\n    class E:\n        def method(self):\n            print(\"No argument passed to `method` of class `E`\")\n\n    class F:\n        def method(self, a):\n            print(f\"Argument {a=} passed to `method` of class `F`\")\n\n    MergedClass = mergeclasses(E, F, invoke_all=[\"method\"])\n    MergedClass().method(\"Alpha\")\n\n    # No argument passed to `method` of class `E`\n    # Argument a='Alpha' passed to `method` of class `F`\n\n\nDynamic Decorators\n------------------\n\nMeta decorator ``decoratewith`` can be used to decorate a class method with one\nor more chained dynamic decorators, regardless whether they statically exist\nor not. Additionally, the syntax of the dynamic decorators aims to get rid of\nthe boilerplate for wrapping and returning the decorator code, leaving just the\nwrapper's code. For example, dynamic decorators can be used to decorate a method\nof a base class with a method of an extension class:\n\n.. code:: python\n\n    from dyndesign import decoratewith, mergeclasses\n\n    class Base:\n        @decoratewith(\"decorator\")\n        def m(self):\n            print(f\"Method `m` of class `Base`\")\n\n    class Ext:\n        def decorator(self, func):\n            print(\"Beginning of method decoration from Ext.\")\n            func(self)\n            print(\"End of method decoration from Ext.\")\n\n    merged = mergeclasses(Base, Ext)()\n    merged.m()\n\n    # Beginning of method decoration from Ext.\n    # Method `m` of class `Base`\n    # End of method decoration from Ext.\n\nIf a decorator name is passed in the ``invoke_all`` argument of\n``mergeclasses``, then multiple decorator instances with the same name from\ndifferent extension classes may be used in chain:\n\n.. code:: python\n\n    class Ext2:\n        def decorator(self, func):\n            print(\"Beginning of method decoration from Ext2.\")\n            func(self)\n            print(\"End of method decoration from Ext2.\")\n\n    merged = mergeclasses(Base, Ext, Ext2, invoke_all=[\"decorator\"])()\n    merged.m()\n\n    # Beginning of method decoration from Ext.\n    # Beginning of method decoration from Ext2.\n    # Method `m` of class `Base`\n    # End of method decoration from Ext2.\n    # End of method decoration from Ext.\n\n\nArguments of ``decoratewith`` are loaded at runtime as properties of the\nvariable 'self': a dynamic decorator can be, for example, a method of a\ncomponent class. In case of dynamic decoration from a sub-instance of 'self',\nthe instance object of the decorated method is passed to the decorator as the\nargument ``decorated_self``. If a dynamic decorator is not found at runtime\n(e.g., because it is a method of an optional class that has not been merged),\nthen the code execution proceeds normally, as shown below with the decorator\n``non_existent_decorator``:\n\n.. code:: python\n\n    class Base:\n        def __init__(self):\n            self.comp = Component()\n\n        @decoratewith(\"comp.decorator1\", \"comp.decorator2\", \"non_existent_decorator\")\n        def m(self):\n            print(\"Method `m` of class `Base`\")\n\n    class Component:\n        def __init__(self):\n            self.value = \"Initial\"\n\n        def decorator1(self, func, decorated_self):\n            print(f\"Beginning of method decoration #1 ({self.value=})\")\n            self.value = \"Processed\"\n            func(decorated_self)\n            print(\"End of method decoration #1\")\n\n        def decorator2(self, func, decorated_self):\n            print(f\"Beginning of method decoration #2 ({self.value=})\")\n            func(decorated_self)\n            print(\"End of method decoration #2\")\n\n    base = Base()\n    base.m()\n\n    # Beginning of method decoration #1 (self.value='Initial')\n    # Beginning of method decoration #2 (self.value='Processed')\n    # Method `m` of class `Base`\n    # End of method decoration #2\n    # End of method decoration #1\n\n\nSafezone Context Manager\n------------------------\n\nAny function or method that may or may not exist at runtime (e.g., methods of\nmerged classes) can be invoked from Context Manager ``safezone`` in order to\nsuppress the possible exceptions raised if the function or method is not found\nat runtime. Optionally, a fallback function/method can be also passed. If no\nfunction name(s) is passed as argument of ``safezone``, then each function in\nthe safe zone's code is protected; if any function name(s) is passed, the\nprotection is restricted to the functions having that/those name(s). For\nexample, ``safezone`` can be used to safely call functions that may or may not\nexist at runtime:\n\n.. code:: python\n\n    from dyndesign import safezone\n\n    def fallback():\n        print(\"Fallback function\")\n\n    def function_a():\n        print(\"Function `a`\")\n\n    with safezone(fallback=fallback):\n        function_a()\n        non_existent_function()\n\n    # Function `a`\n    # Fallback function\n\n\nA further example shows that ``safezone`` can be used to safely invoke methods\nof classes that may or may not be merged with other classes:\n\n.. code:: python\n\n    class Base:\n        def fallback(self):\n            print(\"Fallback method\")\n\n        def m(self, class_desc):\n            print(f\"Method `m` of {class_desc}\")\n            with safezone(\"optional_method\", fallback=self.fallback):\n                self.optional_method()\n\n    class ExtOptional:\n        def optional_method(self):\n            print(\"Optional method from class `ExtOptional`\")\n\n    merged = mergeclasses(Base, ExtOptional)()\n    merged.m(\"merged class\")\n    base = Base()\n    base.m(\"class `Base` standalone\")\n\n    # Method `m` of merged class\n    # Optional method from class `ExtOptional`\n    # Method `m` of class `Base` standalone\n    # Fallback method\n\n\nInvoking methods safely\n-----------------------\n\nAs an alternative to ``safezone`` context manager, ``safeinvoke`` API can be\nused to safely invoke methods that may or may not exist at runtime. To this end,\nmethod ``m`` of class ``Base`` of the example above can be replaced as follows:\n\n.. code:: python\n\n    from dyndesign import safeinvoke\n\n    ...\n\n        def m(self, class_desc):\n            print(f\"Method `m` of {class_desc}\")\n            safeinvoke(\"optional_method\", self, fallback=self.fallback)\n\n\nSingleton classes\n-----------------\n\nSingleton classes can be swiftly created with `SingletonMeta` metaclass and then\ndestroyed with `destroy_singleton`:\n\n.. code:: python\n\n    from dyndesign import SingletonMeta\n\n    class Singleton(metaclass=SingletonMeta):\n        def __init__(self, instance_id = None):\n            if instance_id:\n                self.instance_id = instance_id\n            print(f\"Created a {instance_id} instance of `Singleton`\")\n\n        def where_points(self, object_name):\n            print(f\"Object `{object_name}` points to the {self.instance_id} instance\")\n\n    s_A = Singleton(\"first\")\n    s_A.where_points(\"s_A\")\n\n    # Created a first instance of `Singleton`\n    # Object `s_A` points to the first instance\n\n    s_B = Singleton(\"second\")\n    s_B.where_points(\"s_B\")\n\n    # Object `s_B` points to the first instance\n\n    Singleton().destroy_singleton()\n    s_C = Singleton(\"second\")\n    s_C.where_points(\"s_C\")\n\n    # Created a second instance of `Singleton`\n    # Object `s_C` points to the second instance\n\nThe class method ``destroy`` of SingletonMeta can be invoked to destroy all the\nSingleton classes at once. As a further alternative to the instance call\n``destroy_singleton``, the names of the Singleton classes to destroy can be\npassed to the class method ``destroy``:\n\n.. code:: python\n\n    Singleton().destroy_singleton() # Destroy only `Singleton`\n    SingletonMeta.destroy() # Destroy all the singleton classes\n    SingletonMeta.destroy('Singleton1', 'Singleton2', 'Singleton3') # Destroy selectively\n\n\nImporting classes dynamically\n-----------------------------\n\nClasses can be imported dynamically using the package/class names or the path in\ndot-notation as shown below:\n\n.. code:: python\n\n    from dyndesign import importclass\n\n    ClassA = importclass('package_A', 'ClassA')\n    ClassB = importclass('directory_B.package_B.ClassB')\n\n\nRunning tests\n-------------\n\nTo run the tests using your default python interpreter:\n\n::\n\n    pip install -U pytest\n    python -m pytest test\n\n\n.. |Build Status| image:: https://github.com/amarula/dyndesign/actions/workflows/python-app.yml/badge.svg\n    :target: https://github.com/amarula/dyndesign/actions\n.. |Python Version Status| image:: https://img.shields.io/badge/python-3.8_3.9_3.10_3.11-blue.svg\n    :target: https://github.com/amarula/dyndesign/actions\n.. |PyPi Version Status| image:: https://badge.fury.io/py/dyndesign.svg\n    :target: https://badge.fury.io/py/dyndesign\n.. |License| image:: https://img.shields.io/badge/License-MIT-yellow.svg\n    :target: https://opensource.org/licenses/MIT\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Toolset for Dynamic Design in Python.",
    "version": "1.1.0",
    "project_urls": {
        "Homepage": "https://github.com/amarula/dyndesign",
        "Repository": "https://github.com/amarula/dyndesign"
    },
    "split_keywords": [
        "design",
        "dynamic",
        "decorator",
        "inheritance"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6fd4174f9b06491a58bb8eb2af5aca4798b03e1da12e411fd12e6ee34f8cd903",
                "md5": "67087536a0b25b90b845ccb0daf04dc0",
                "sha256": "aa22a36054b4a3b4d1bf4838fefb56e760374bc687a647c27f6b927c5127af04"
            },
            "downloads": -1,
            "filename": "dyndesign-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "67087536a0b25b90b845ccb0daf04dc0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 35781,
            "upload_time": "2023-09-22T14:40:20",
            "upload_time_iso_8601": "2023-09-22T14:40:20.928586Z",
            "url": "https://files.pythonhosted.org/packages/6f/d4/174f9b06491a58bb8eb2af5aca4798b03e1da12e411fd12e6ee34f8cd903/dyndesign-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "851f1601c979ab61836d352b1f1e285d2e0370d81cea9ec69af10462d538ab28",
                "md5": "866f582381a92002dfdd1015631d8c9c",
                "sha256": "ca1632c9c2c988840d974b4a2bf8834cec1cbbd084703a8be4a89b737504176e"
            },
            "downloads": -1,
            "filename": "dyndesign-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "866f582381a92002dfdd1015631d8c9c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 30548,
            "upload_time": "2023-09-22T14:40:23",
            "upload_time_iso_8601": "2023-09-22T14:40:23.027627Z",
            "url": "https://files.pythonhosted.org/packages/85/1f/1601c979ab61836d352b1f1e285d2e0370d81cea9ec69af10462d538ab28/dyndesign-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-09-22 14:40:23",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "amarula",
    "github_project": "dyndesign",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "dyndesign"
}
        
Elapsed time: 2.14184s