# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['dyndesign']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'dyndesign',
    'version': '0.9.2',
    'description': 'Toolset for Dynamic Design Patterns in Python.',
    'long_description': 'DynDesign\n=========\n\n|Build Status| |PyPi Version Status| |Python Version Status| |License|\n\nA set of tools for Dynamic Design Patterns in Python.\n\n\nInstall\n-------\n\nDyndesign is on the Python Package Index (PyPI):\n\n::\n\n    pip install dyndesign\n\n\nOverview\n--------\n\nMerge two or more classes:\n\n.. code:: python\n\n    from dyndesign import mergeclasses\n\n    MergedClass = mergeclasses(Base, Ext1, Ext2, ...)\n\nDecorate 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\nSafely invoke functions or methods from a ``safezone`` context manager:\n\n.. code:: python\n\n    from dyndesign import safezone\n\n    with safezone():\n        ...\n        function_possibly_non_existent()\n        ...\n\nCreate 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\nImport 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\nMerging Classes\n---------------\n\nDyndesign provides API ``mergeclasses`` to merge two or more classes as if they\nwere dictionaries, so that the merged class has the attributes and methods of\nthe base class and of the extension classes. If two or more classes have the\nsame attributes/methods, the attributes/methods of the rightmost classes (in the\norder in which they are passed to ``mergeclasses``) overwrite the leftmost,\nsimilarly to what happens when merging dictionaries.\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 takes just the arguments it needs (i.e., the arguments in its\nsignature):\n\n.. code:: python\n\n    from dyndesign import mergeclasses\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\n\nDynamic Decorators\n------------------\n\nMeta decorator ``decoratewith`` decorates a class method with one or more\npipelined instance decorators (regardless whether they statically exist or not).\nThe syntax of the dynamic decorators aims to get rid of the boilerplate for\nwrapping and returning the decorator code, leaving just the wrapper\'s code. For\nexample, dynamic decorators can be used to decorate a method from a base\nclass with a method from an extension class:\n\n.. code:: python\n\n    from dyndesign import decoratewith\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.")\n            func(self)\n            print("End of method decoration.")\n\n    merged = mergeclasses(Base, Ext)()\n    merged.m()\n\n    # Beginning of method decoration.\n    # Method `m` of class `Base`\n    # End of method decoration.\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    from dyndesign import decoratewith\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    from dyndesign import safezone\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 alternative to ``safezone`` context manager, ``safeinvoke`` can be used to\nsafely invoke methods that may or may not exist at runtime. To this end, method\n``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 and destroyed:\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    s_B = Singleton()\n    s_B.where_points("s_B")\n    Singleton().destroy_singleton()\n    s_C = Singleton("second")\n    s_C.where_points("s_C")\n\n    # Created a first instance of `Singleton`\n    # Object `s_A` points to the first instance\n    # Object `s_B` points to the first instance\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 the\nSingleton classes all at once. As a further alternative to the instance call\n``Singleton().destroy_singleton()``, the names of the Singleton classes to\ndestroy can be passed to the class method ``destroy``:\n\n.. code:: python\n\n    Singleton().destroy_singleton()\n    SingletonMeta.destroy()\n    SingletonMeta.destroy(\'Singleton1\', \'Singleton2\', \'Singleton3\')\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:\n\n::\n\n    pip install -U pytest\n    python3 -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',
    'author': 'Patrizio Gelosi',
    'author_email': 'patrizio.gelosi@amarulasolutions.com',
    'maintainer': 'Patrizio Gelosi',
    'maintainer_email': 'patrizio.gelosi@amarulasolutions.com',
    'url': 'https://github.com/amarula/dyndesign',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
