Metadata-Version: 1.1
Name: extend_me
Version: 1.1.1
Summary: Class based extension/plugin library
Home-page: https://github.com/katyukha/extend-me
Author: Dmytro Katyukha
Author-email: firemage.dima@gmail.com
License: GPL
Description: .. image:: https://travis-ci.org/katyukha/extend-me.svg?branch=master
            :target: https://travis-ci.org/katyukha/extend-me
        
        
        .. image:: https://coveralls.io/repos/katyukha/extend-me/badge.png
            :target: https://coveralls.io/r/katyukha/extend-me
        
        
        Extend Me - Class based extension/plugin library
        ================================================
        
        This module provides mechanism of extension of your application
        based on 'extension via inheritance'. Under this words I mean
        ability to define new extensions of application objects simply
        by subclassing of extensible classes of app.
        
        For example we have app with class 'Worker' which we would like
        to make extensible (allowing third party modules to extend or
        change its behavior). Thinking strait, there are a lot of work
        to be done, to impelement mechanism of loading, registering,
        end enabling extension, with lot of glue code, which must define
        some entry points to connect extension and main app. But why not
        make it simpler, supposing that any subclass of 'Worker' will
        extend it? And this module provides implementation of this
        in two ways:
        
            - Explicit (by using metaclass *ExtensibleType* directly)
        
                - When using this way You will heve seperatly Base class
                  to be subclassed by extension classes and class getter
                  which will construct class based on all defined extensions
                  using multiple inhertance
        
            - Implicit (by using Extensible class which use metaclass
              magic implicitly)
        
                - *Extensible* class takes care of all metaclass magic
                  related to generation objects of correct class
        
        
        How it Works
        ------------
        
        Metaclass (*ExtensibleType*) tracks all subclasses of class it
        is applied to, and provides method to build class based on all
        subclasses of base class, thus using all functionality of all
        subclasses. Thus generation of correct class is separate process
        which should be used everywhere where extensible class is requred.
        
        To simplify this class *Extensible* was implemented. It has redefined
        method *__new__* which automaticaly creates instances of correct class
        (class that inherited from base class and all its extensions')
        
        
        Examples
        --------
        
        ExtensibleType
        ~~~~~~~~~~~~~~
        
        At the begining we should create a metaclass that will automaticaly
        gether all information about all extensions, and apply this metaclass
        to class we would like to enable extensions for::
        
            >>> import six  # Used for Python 2/3 compatability
            >>> mc = ExtensibleType._("Object")
        
            >>> @six.add_metaclass(mc)
            ... class Object(object):
            ...     pass
        
        Not method *_* of *ExtensibleType*. This method is used to create metaclass
        for specific object. It receives one argument - string that will be used as
        name of class generated by this metaclass
        
        Next we may define extension for this class. It is very simple.
        Just subclass previously defined class::
        
            >>> class ObjectExtension(Object):
            ...     cool_attribute = 1
            ...     def method1(self):
            ...         return "Test"
        
        So... at this momet we have base class and extension. And here is that
        core magic occures. Metaclass that was created at the begining automaticaly
        collects all subclasses of base class. So it is posible now to create new
        class that is subclass of all subclasses of base class using multiple inheritance.
        And metaclass *mc* will do it for You::
        
            >>> cls = mc.get_class()
        
        And now You can use cls for Your needs, instead of base class.
        It can do all that base class can, and all that extensions can::
        
            >>> obj = cls()
            >>> obj.method1()
            'Test'
            >>> obj.cool_attribute
            1
        
        
        ExtensibleByHashType
        ~~~~~~~~~~~~~~~~~~~~
        
        Same as *ExtensibleType*, but allows to build tree of classes
        for diferent names (types). Just look examples below.
        
        First, create metaclass that will specify inheritance rules::
        
            >>> import six  # Used for Python 2/3 compatability
            >>> mc = ExtensibleByHashType._("Connector", hashattr='name')
        
        Here we see aditional parametr in _ method: ``hashattr='name'``
        which describes what meta attribute will be used as key(hash).
        
        Next step - we have to create Base class with this metaclass.
        As example we will look into connection classes of *openerp_proxy* project::
        
            >>> @six.add_metaclass(mc)
            ... class ConnectorBase(object):
            ...     # Base class for all connectors
            ...
            ...     def __init__(self, host, port, verbose=False):
            ...         self.host = host
            ...         self.port = port
            ...         self.verbose = verbose
            ...
            ...     def _get_service(self, name):
            ...         raise NotImplementedError
            ...
            ...     def get_service(self, name):
            ...         # Returns service for specified *name*
            ...         return self._get_service(name)
        
        Base class describes only interface, and may be some part of abstract logic
        And as next step we will extend it in diferent ways to support different
        connection types::
        
            >>> class ConnectorXMLRPC(ConnectorBase):
            ...     # XML-RPC connector
            ...     class Meta:
            ...         name = 'xml-rpc' # remember definition of metaclass?
            ...                          # this attribute is used as hash(key)
            ...                          # to unique identify each banch of extensions
            ...                          # of base class
            ...
            ...     def __init__(self, *args, **kwargs):
            ...         super(ConnectorXMLRPC, self).__init__(*args, **kwargs)
            ...         self.__services = {}
            ...
            ...     def get_service_url(self, service_name):
            ...         return 'http://%s:%s/xmlrpc/%s' % (self.host, self.port, service_name)
            ...
            ...     def _get_service(self, name):
            ...         service = self.__services.get(name, False)
            ...         if service is False:
            ...             service = XMLRPCProxy(self.get_service_url(name), verbose=self.verbose)
            ...             self.__services[name] = service
            ...         return service
            ...
            ...
            ... # Pay attention on base class.
            >>> class ConnectorXMLRPCS(ConnectorXMLRPC):
            ...     # XML-RPCS Connector
            ...     class Meta:
            ...         name = 'xml-rpcs'
            ...
            ...     def get_service_url(self, service_name):
            ...         return 'https://%s:%s/xmlrpc/%s' % (self.host, self.port, service_name)
        
        Code above creates two connectors: one for *XML-RPC* and one for *XML-RPCS*.
        Each of connectors may be extended by simple inheritance. And if required any
        extension may define new branch(key)(hash) as wee see in example above.
        
        To use this connector *mc* has method *get_class(name[, default=False])*
        wich will return class generated for hash=*name*::
        
            >>> cls = mc.get_class('xml-rpc')
            >>> [b.__name__ for b in cls.__bases__]
            ['ConnectorXMLRPC', 'ConnectorBase']
            >>> cls.__name__
            'Connector'
        
            >>> cls = mc.get_class('xml-rpcs')
            >>> [b.__name__ for b in cls.__bases__]
            ['ConnectorXMLRPCS', 'ConnectorBase']
            >>> cls.__name__
            'Connector'
        
        Example above shows what classes will be generated for specified names.
        By default, if *mc.get_class* called with unregistered name
        (No extension with ``Meta.name == name`` defined) it will raise *ValueError*
        
        If You want to allow creating of classes with not *Meta.name* defined,
        just pass ``default=True`` to *mc.get_class*::
        
            >>> cls = mc.get_class('unexisting-protocol', default=True)
            >>> [b.__name__ for b in cls.__bases__]
            ['ConnectorBase']
            >>> cls.__name__
            'Connector'
        
        
        Extensible
        ~~~~~~~~~~
        
        This class provides one more level of abstraction, allowing to hide all metaclass magic
        behide the scene. So, using it You don't need to worry about metaclasses and class
        creation process. Just inherit extensions form base class, and use in Your program
        instances of base class. Let's see it in example::
        
            >>> class MyCoolClass(Extensible):
            ...     my_attr_1 = 25
            ...     def my_method1(self, arg1):
            ...         print('Hello, %s' % arg1)
        
            >>> class MyCoolClassExtension1(MyCoolClass):
            ...     def my_method1(self, arg1):
            ...         super(MyCoolClassExtension1, self).my_method1(arg1.upper())
            ...
            ...     def my_method2(self, arg1):
            ...         print("Good by, %s" % arg1)
        
        And now using simply instances of base class You have all abilities that provided by extensions::
        
            >>> my_cool_obj = MyCoolClass()
            >>> print(my_cool_obj.my_attr_1)
            25
            >>> my_cool_obj.my_method1('World')
            Hello, WORLD
            >>> my_cool_obj.my_method2('World')
            Good by, World
        
        
Keywords: extension,plugin
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
