{ "info": { "author": "Dmytro Katyukha", "author_email": "dmytro.katyukha@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": ".. image:: https://travis-ci.org/katyukha/extend-me.svg?branch=master\n :target: https://travis-ci.org/katyukha/extend-me\n\n.. image:: https://coveralls.io/repos/katyukha/extend-me/badge.png\n :target: https://coveralls.io/r/katyukha/extend-me\n\n.. image:: https://img.shields.io/badge/docs-passing-brightgreen.svg\n :target: https://katyukha.gitlab.io/extend-me/\n\n\nExtend Me - Class based extension/plugin library\n================================================\n\nThis module provides mechanism of extension of your application\nbased on 'extension via inheritance'. Under this words I mean\nability to define new extensions of application objects simply\nby subclassing of extensible classes of app.\n\nFor example we have app with class 'Worker' which we would like\nto make extensible (allowing third party modules to extend or\nchange its behavior). Thinking strait, there are a lot of work\nto be done, to impelement mechanism of loading, registering,\nend enabling extension, with lot of glue code, which must define\nsome entry points to connect extension and main app. But why not\nmake it simpler, supposing that any subclass of 'Worker' will\nextend it? And this module provides implementation of this\nin two ways:\n\n- Explicit (by using metaclass *ExtensibleType* directly)\n - When using this way You will heve seperatly Base class\n to be subclassed by extension classes and class getter\n which will construct class based on all defined extensions\n using multiple inhertance\n- Implicit (by using Extensible class which use metaclass magic implicitly)\n - *Extensible* class takes care of all metaclass magic\n related to generation objects of correct class\n\n\nHow it Works\n------------\n\nMetaclass (*ExtensibleType*) tracks all subclasses of class it\nis applied to, and provides method to build class based on all\nsubclasses of base class, thus using all functionality of all\nsubclasses. Thus generation of correct class is separate process\nwhich should be used everywhere where extensible class is requred.\n\nTo simplify this class *Extensible* was implemented. It has redefined\nmethod *__new__* which automaticaly creates instances of correct class\n(class that inherited from base class and all its extensions')\n\n\nExamples\n--------\n\nExtensibleType\n~~~~~~~~~~~~~~\n\nAt the begining we should create a metaclass that will automaticaly\ngether all information about all extensions, and apply this metaclass\nto class we would like to enable extensions for::\n\n >>> import six # Used for Python 2/3 compatability\n >>> mc = ExtensibleType._(\"Object\")\n\n >>> @six.add_metaclass(mc)\n ... class Object(object):\n ... pass\n\nNot method *_* of *ExtensibleType*. This method is used to create metaclass\nfor specific object. It receives one argument - string that will be used as\nname of class generated by this metaclass\n\nNext we may define extension for this class. It is very simple.\nJust subclass previously defined class::\n\n >>> class ObjectExtension(Object):\n ... cool_attribute = 1\n ... def method1(self):\n ... return \"Test\"\n\nSo... at this momet we have base class and extension. And here is that\ncore magic occures. Metaclass that was created at the begining automaticaly\ncollects all subclasses of base class. So it is posible now to create new\nclass that is subclass of all subclasses of base class using multiple inheritance.\nAnd metaclass *mc* will do it for You::\n\n >>> cls = mc.get_class()\n\nAnd now You can use cls for Your needs, instead of base class.\nIt can do all that base class can, and all that extensions can::\n\n >>> obj = cls()\n >>> obj.method1()\n 'Test'\n >>> obj.cool_attribute\n 1\n\n\nExtensibleByHashType\n~~~~~~~~~~~~~~~~~~~~\n\nSame as *ExtensibleType*, but allows to build tree of classes\nfor diferent names (types). Just look examples below.\n\nFirst, create metaclass that will specify inheritance rules::\n\n >>> import six # Used for Python 2/3 compatability\n >>> mc = ExtensibleByHashType._(\"Connector\", hashattr='name')\n\nHere we see aditional parametr in _ method: ``hashattr='name'``\nwhich describes what meta attribute will be used as key(hash).\n\nNext step - we have to create Base class with this metaclass.\nAs example we will look into connection classes of *openerp_proxy* project::\n\n >>> @six.add_metaclass(mc)\n ... class ConnectorBase(object):\n ... # Base class for all connectors\n ...\n ... def __init__(self, host, port, verbose=False):\n ... self.host = host\n ... self.port = port\n ... self.verbose = verbose\n ...\n ... def _get_service(self, name):\n ... raise NotImplementedError\n ...\n ... def get_service(self, name):\n ... # Returns service for specified *name*\n ... return self._get_service(name)\n\nBase class describes only interface, and may be some part of abstract logic\nAnd as next step we will extend it in diferent ways to support different\nconnection types::\n\n >>> class ConnectorXMLRPC(ConnectorBase):\n ... # XML-RPC connector\n ... class Meta:\n ... name = 'xml-rpc' # remember definition of metaclass?\n ... # this attribute is used as hash(key)\n ... # to unique identify each banch of extensions\n ... # of base class\n ...\n ... def __init__(self, *args, **kwargs):\n ... super(ConnectorXMLRPC, self).__init__(*args, **kwargs)\n ... self.__services = {}\n ...\n ... def get_service_url(self, service_name):\n ... return 'http://%s:%s/xmlrpc/%s' % (self.host, self.port, service_name)\n ...\n ... def _get_service(self, name):\n ... service = self.__services.get(name, False)\n ... if service is False:\n ... service = XMLRPCProxy(self.get_service_url(name), verbose=self.verbose)\n ... self.__services[name] = service\n ... return service\n ...\n ...\n ... # Pay attention on base class.\n >>> class ConnectorXMLRPCS(ConnectorXMLRPC):\n ... # XML-RPCS Connector\n ... class Meta:\n ... name = 'xml-rpcs'\n ...\n ... def get_service_url(self, service_name):\n ... return 'https://%s:%s/xmlrpc/%s' % (self.host, self.port, service_name)\n\nCode above creates two connectors: one for *XML-RPC* and one for *XML-RPCS*.\nEach of connectors may be extended by simple inheritance. And if required any\nextension may define new branch(key)(hash) as wee see in example above.\n\nTo use this connector *mc* has method *get_class(name[, default=False])*\nwich will return class generated for hash=*name*::\n\n >>> cls = mc.get_class('xml-rpc')\n >>> [b.__name__ for b in cls.__bases__]\n ['ConnectorXMLRPC', 'ConnectorBase']\n >>> cls.__name__\n 'Connector'\n\n >>> cls = mc.get_class('xml-rpcs')\n >>> [b.__name__ for b in cls.__bases__]\n ['ConnectorXMLRPCS', 'ConnectorBase']\n >>> cls.__name__\n 'Connector'\n\nExample above shows what classes will be generated for specified names.\nBy default, if *mc.get_class* called with unregistered name\n(No extension with ``Meta.name == name`` defined) it will raise *ValueError*\n\nIf You want to allow creating of classes with not *Meta.name* defined,\njust pass ``default=True`` to *mc.get_class*::\n\n >>> cls = mc.get_class('unexisting-protocol', default=True)\n >>> [b.__name__ for b in cls.__bases__]\n ['ConnectorBase']\n >>> cls.__name__\n 'Connector'\n\n\nExtensible\n~~~~~~~~~~\n\nThis class provides one more level of abstraction, allowing to hide all metaclass magic\nbehide the scene. So, using it You don't need to worry about metaclasses and class\ncreation process. Just inherit extensions form base class, and use in Your program\ninstances of base class. Let's see it in example::\n\n >>> class MyCoolClass(Extensible):\n ... my_attr_1 = 25\n ... def my_method1(self, arg1):\n ... print('Hello, %s' % arg1)\n\n >>> class MyCoolClassExtension1(MyCoolClass):\n ... def my_method1(self, arg1):\n ... super(MyCoolClassExtension1, self).my_method1(arg1.upper())\n ...\n ... def my_method2(self, arg1):\n ... print(\"Good by, %s\" % arg1)\n\nAnd now using simply instances of base class You have all abilities that provided by extensions::\n\n >>> my_cool_obj = MyCoolClass()\n >>> print(my_cool_obj.my_attr_1)\n 25\n >>> my_cool_obj.my_method1('World')\n Hello, WORLD\n >>> my_cool_obj.my_method2('World')\n Good by, World\n", "description_content_type": "", "docs_url": "https://pythonhosted.org/extend_me/", "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/katyukha/extend-me", "keywords": "extension", "license": "MPL 2.0", "maintainer": "", "maintainer_email": "", "name": "extend_me", "package_url": "https://pypi.org/project/extend_me/", "platform": "", "project_url": "https://pypi.org/project/extend_me/", "project_urls": { "Homepage": "https://github.com/katyukha/extend-me" }, "release_url": "https://pypi.org/project/extend_me/1.1.4/", "requires_dist": null, "requires_python": "", "summary": "Class based extension/plugin library", "version": "1.1.4" }, "last_serial": 4036158, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "356dc18e4fd0f80ea1aedeb5ec0cb425", "sha256": "d650199b688d2b87ddf463e2d09f1b23a5a779f61fe7c0768731d215f023ceb9" }, "downloads": -1, "filename": "extend_me-1.0.0.tar.gz", "has_sig": false, "md5_digest": "356dc18e4fd0f80ea1aedeb5ec0cb425", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5130, "upload_time": "2014-07-23T11:11:18", "url": "https://files.pythonhosted.org/packages/1a/e2/d4185f08500ce79382166656f53b495d743df9a47184244e10485b54228a/extend_me-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "36dd1e3b6886f4fae1363f42e4a92a57", "sha256": "f8de93ba8d73600347eb91a92b13a0d6ddbe32b368c447ba7d65d078835b062a" }, "downloads": -1, "filename": "extend_me-1.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "36dd1e3b6886f4fae1363f42e4a92a57", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 9486, "upload_time": "2014-09-24T16:29:46", "url": "https://files.pythonhosted.org/packages/a5/0d/f40afc7c0f175935c6ce923e18ee1aef5316da2b6d0ea6b32e2e19e5da40/extend_me-1.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "64aad3b42a662fe8621fd0ff4d4e5afd", "sha256": "37a2bb38b78cc6fe33dce36f9a4449255485e260ecb8ad8e5b7cb2f8d9e8c7e8" }, "downloads": -1, "filename": "extend_me-1.1.0.tar.gz", "has_sig": false, "md5_digest": "64aad3b42a662fe8621fd0ff4d4e5afd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7571, "upload_time": "2014-09-24T16:29:44", "url": "https://files.pythonhosted.org/packages/cd/b9/be38246cf18353a56a396f764950cd0b829920f4068f660b576624cd6bf1/extend_me-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "48ea2b7e5565046e33aeefabbc01fbda", "sha256": "53144ffa54f19934ff7a96ba16b4344f93051ffa4d2b658e50862008d4ede73e" }, "downloads": -1, "filename": "extend_me-1.1.1.tar.gz", "has_sig": false, "md5_digest": "48ea2b7e5565046e33aeefabbc01fbda", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10340, "upload_time": "2015-02-02T12:47:25", "url": "https://files.pythonhosted.org/packages/df/91/29a820ef82984cee104da45d61eb000911859b1f30e25930b34e8b1bdc3c/extend_me-1.1.1.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "2a758032679c69290ccade4a57a60c56", "sha256": "d758ab8545dda3c101b96f7988a43405e52d2463cdcbb6c1de70dd7a6608d564" }, "downloads": -1, "filename": "extend_me-1.1.2.tar.gz", "has_sig": false, "md5_digest": "2a758032679c69290ccade4a57a60c56", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13369, "upload_time": "2015-02-25T15:22:13", "url": "https://files.pythonhosted.org/packages/25/08/3adfe6ee752751368cbaea76dd1ca7f2c740798aba1be905502b5f966c36/extend_me-1.1.2.tar.gz" } ], "1.1.3": [ { "comment_text": "", "digests": { "md5": "94e5de0106b0fa907b51fef2e7820769", "sha256": "db81d733286afe4bc9a0100ffcdc42566ed583ab4c09df7f9414238090bd9898" }, "downloads": -1, "filename": "extend_me-1.1.3.tar.gz", "has_sig": false, "md5_digest": "94e5de0106b0fa907b51fef2e7820769", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13422, "upload_time": "2015-07-21T10:49:37", "url": "https://files.pythonhosted.org/packages/29/11/5b0ae1610b705aaeb604039fbc1efb9eb091aec6be570c2e450531465229/extend_me-1.1.3.tar.gz" } ], "1.1.4": [ { "comment_text": "", "digests": { "md5": "651cd351338d93801729bcb58ca202eb", "sha256": "2333593c4e49a049a1f76447acc2385160140af93f6bbc3d67fe4f63245dbfb8" }, "downloads": -1, "filename": "extend_me-1.1.4-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "651cd351338d93801729bcb58ca202eb", "packagetype": "bdist_wheel", "python_version": "3.6", "requires_python": null, "size": 15807, "upload_time": "2018-07-06T11:50:20", "url": "https://files.pythonhosted.org/packages/26/31/46d44853e439b7f9de538cc25e87f15abe811a0b8baa51d270c6dc0ab00d/extend_me-1.1.4-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2d3149c230ab8ce5878c5e659b856cf6", "sha256": "bda62c5b336a1649a0d2a778c0c5ada5b12450541f99a0f1f4a77cc749bf1845" }, "downloads": -1, "filename": "extend_me-1.1.4.tar.gz", "has_sig": false, "md5_digest": "2d3149c230ab8ce5878c5e659b856cf6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18352, "upload_time": "2018-07-06T11:50:18", "url": "https://files.pythonhosted.org/packages/6c/a0/a979c78ff11c258f1a492aec9105302862fc957088f9edc5f48bb483b603/extend_me-1.1.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "651cd351338d93801729bcb58ca202eb", "sha256": "2333593c4e49a049a1f76447acc2385160140af93f6bbc3d67fe4f63245dbfb8" }, "downloads": -1, "filename": "extend_me-1.1.4-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "651cd351338d93801729bcb58ca202eb", "packagetype": "bdist_wheel", "python_version": "3.6", "requires_python": null, "size": 15807, "upload_time": "2018-07-06T11:50:20", "url": "https://files.pythonhosted.org/packages/26/31/46d44853e439b7f9de538cc25e87f15abe811a0b8baa51d270c6dc0ab00d/extend_me-1.1.4-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2d3149c230ab8ce5878c5e659b856cf6", "sha256": "bda62c5b336a1649a0d2a778c0c5ada5b12450541f99a0f1f4a77cc749bf1845" }, "downloads": -1, "filename": "extend_me-1.1.4.tar.gz", "has_sig": false, "md5_digest": "2d3149c230ab8ce5878c5e659b856cf6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18352, "upload_time": "2018-07-06T11:50:18", "url": "https://files.pythonhosted.org/packages/6c/a0/a979c78ff11c258f1a492aec9105302862fc957088f9edc5f48bb483b603/extend_me-1.1.4.tar.gz" } ] }