{ "info": { "author": "Michael McCartney", "author_email": "mccartneyworks@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "purepy\n======\nPure virtual class functionality in Python.\n\nA _very_ small metaclass to do some of the testing for us.\n\nBranch | Status | Coverage\n--- | --- | --\n`master` | [![Build Status](https://travis-ci.org/mccartnm/purepy.svg?branch=master)](https://travis-ci.org/mccartnm/purepy) | [![Code Coverage](https://codecov.io/gh/mccartnm/purepy/branch/master/graph/badge.svg)](https://codecov.io/gh/mccartnm/purepy)\n`dev` | [![Build Status](https://travis-ci.org/mccartnm/purepy.svg?branch=dev)](https://travis-ci.org/mccartnm/purepy) | [![Code Coverage](https://codecov.io/gh/mccartnm/purepy/branch/dev/graph/badge.svg)](https://codecov.io/gh/mccartnm/purepy)\n\n\n\n## The What\nIn C++ and other strong typed, OOP, languages, we use virtual classes and pure virtual classes to help\nhandle some incredibly cool paradigms when it comes to plugin design, object organization and more.\n\nPython thinks of _everything_ as a virtual class. Which is great because polymorphism doesn't\nrequire us to explicitly set which functions are virtual or overloaded but instead just works!\n\nThis is awesome until it's not.\n\n## The Why\nSo, with this knowledge, you ask, \"Why bother with pure virtual classes? There are plenty of reasons\nnot to use this in Python.\" You would be right! There's plenty of reasons _not_ to use/need this tool.\n\nBut, when the need arises, you may just find this quite helpful. For us, we found it most useful when\nwe were integrating an API into multiple third party applications and wanted to assure ourselves we had\nthe right functionality and signatures without needing to write additional test code or wait for the\ninterpreter to make an instance of an ABCMeta object for it to fail.\n\n## The Advantage\nWe first took a stab with the [`abc.ABCMeta`][2] object from Pythons default libs but ran into the issue of\n\n> I can do whatever I want and _until_ the object is made, it will be wrong!\n\nWhich is good sometimes, because it allows for crazy stuff like `setattr()` and dynamic class building\n_but_, when it comes to integration of an app, there's usually less desire for out-there solutions like\n`__setitem__` or `setattr()`.\n\nWe want the interpreter, as soon as it loads our class into memory, to alert us if it's not \"up to\ncode\" and tell us what we need to fix about it. This is very \"preprocessor\" like and it has some major\nadvantages with a few caveats.\n\n# Basic Example\n\nGiven the following:\n```python\nfrom purepy import PureVirtualMeta, pure_virtual\n\nclass Interface(metaclass=PureVirtualMeta):\n\n @pure_virtual\n def save(self, filepath=None):\n raise NotImplementedError()\n\n @pure_virtual\n def load(self, filepath=None):\n raise NotImplementedError()\n\nclass Overload(Interface):\n\n def save(self, filepath=None):\n print (\"Saving\")\n```\n\nIf we put this into the interpreter, without even creating an instance of the Overload class, we\nwould get:\n\n```python\n# ...\n# PureVirtualError: Virtual Class Declaration:\n# - 'Overload': The following pure virtual functions must be overloaded from\n# base: 'Interface' before class can be used:\n# - def load(self, filepath=None)\n```\n\nWe got that error without having to execute any manual code or writing a test. This may not be the way\nyou want to work, at which point you don't need this utility!\n\n# Additional Features\n\nTo act like a proper pure virtual class, `PureVirtualMeta` and the default `pure_virtual` utilities are\n_extremely_ strict when it comes to working with the classes. There are a wide variety of ways to augment\nthis however as described below.\n\n### Signature Verification\n\nBy default `purepy` will assert that the signatures of the `pure_virtual` function match the\noverloaded.\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n\n @pure_virtual\n def save(self, filepath=None):\n raise NotImplementedError()\n\nclass Overload(Interface):\n\n def save(self):\n print (\"Saving\")\n\n# Result:\n# ...\n# PureVirtualError: Virtual Class Declaration:\n# - 'Overload': The following overload functions have the\n# wrong signature from base: 'Interface'\n# - def save(self): -> def save(self, filepath=None):\n```\n\nThis can be disabled by setting the class variable `pv_explicit_args = False`\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n pv_explicit_args = False\n # ...\n```\n\n### Base Instances\n\nBy default `purepy` will mimic the [`abc.abstractmethod`][1] and raise and error when we try to\ninstantiate a pure virtual class.\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n\n @pure_virtual\n def save(self, filepath=None):\n raise NotImplementedError()\n\n>>> Interface()\n# ...\n# PureVirtualError: Cannot instantiate pure virtual class\n# 'Interface' with pure virtual functions: (save)\n```\n\nThis can be disabled with the class variable `pv_allow_base_instance = True`\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n pv_allow_base_instance = True\n\n @pure_virtual\n def save(self, filepath=None):\n raise NotImplementedError()\n\n>>> print(Interface())\n# <__main__.Interface object at ...>\n```\n\n### Forced NotImplementedError\n\nBy default, the `pure_virtual` decorator will force all it's functions to raise a `NotImplementedError` even\nwhen there is information defined and the class can be instantiated.\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n pv_allow_base_instance = True\n\n @pure_virtual\n def save(self, filepath=None):\n print (\"Saving \", str(filepath))\n>>> inst = Interface()\n>>> inst.save(\"foo\")\n# ...\n# NotImplementedError: Illegal call to pure virtual function save\n```\n\nThis can be disabled with a custom decorator by setting `force_not_implemented = False`.\n\n```python\nmy_pure_virtual = PureVirtualMeta.new(force_not_implemented=False)\n\nclass Interface(metaclass=PureVirtualMeta):\n pv_allow_base_instance = True\n\n @my_pure_virtual\n def save(self, filepath=None):\n print (\"Saving \", filepath)\n>>> inst = Interface()\n>>> inst.save(\"foo\")\n# Saving foo\n```\n\n# Customized Decorator\n\nBy default, the `pure_virtual` decorator provided is quite strict. In some cases you may want to\naugment the properties to make it more forgiving. This can be done with the `PureVirtualMeta.new()`\nand `PureVirtualMeta.new_class()` functions. Both functions take additional `**kwargs` that augment the\ndecorator and subsequent validation.\n\n```python\nmy_pure_virtual = PureVirtualMeta.new(strict_types=False)\n\nclass Interface(metaclass=PureVirtualMeta):\n\n @my_pure_virtual\n def foo(self, filepath: str):\n raise NotImplementedError()\n\nclass Overload(Interface):\n\n # This is NOT okay by default, but okay with our custom decorator \n def foo(self, filepath):\n pass\n```\n\n# Registry\nThere are two ways to control/retrieve the pure virtual functions available in the api.\n\n## From Id\nEach `pure_virtual` decorator gets a unique identifier and all functions it its registry are handled\nunderneath that.\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n\n @pure_virtual\n def foo(self, filepath):\n raise NotImplementedError()\n\nprint (PureVirtualMeta.virtual_functions_from_id(pure_virtual.id()))\n# []\n```\n\n## From Class\nEach class registers the pure virtual functions and can be polled by both the class and an instance of\nsaid class.\n\n```python\nclass Interface(metaclass=PureVirtualMeta):\n\n pv_allow_base_instance = True\n\n @pure_virtual\n def foo(self, filepath):\n raise NotImplementedError()\n\nprint (PureVirtualMeta.pure_virtual_functions(Interface))\n# []\nprint (PureVirtualMeta.pure_virtual_functions(Interface()))\n# []\nprint (PureVirtualMeta.is_pure_virtual_class(Interface))\n# True\n```\n\n# Override Decorator\nFor clarity, we may want to decorate the overloaded functions. In C++ we use something like:\n\n```cpp\n void myFunction(int variable) override;\n```\n\n`purepy` provides the `override` decorator this this purpose.\n\n```python\nfrom purepy import override\nclass Overload(Interface):\n\n @override()\n def foo(self, filepath):\n print (\"This is overloaded\")\n```\n\n> Note: You _must_ call the override decorator, even with no arguments, to setup the proper function\n> binding.\n\nIn the future, this may to be used to further augment the functionality of overloaded functions.\n\n[1]:(https://docs.python.org/3/library/abc.html#abc.abstractmethod)\n[2]:(https://docs.python.org/3/library/abc.html)\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/mccartnm/purepy", "keywords": "preprocess,functions,pure,virtual,metaclass,abstract", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "purepy", "package_url": "https://pypi.org/project/purepy/", "platform": "", "project_url": "https://pypi.org/project/purepy/", "project_urls": { "Homepage": "https://github.com/mccartnm/purepy" }, "release_url": "https://pypi.org/project/purepy/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "Minor utilites for developing pure virtual classes.", "version": "1.0.0" }, "last_serial": 5193578, "releases": { "0.8.0": [ { "comment_text": "", "digests": { "md5": "3d2ffcfc2192ffbd25fe7df820f4f344", "sha256": "ae020d9172d926619b6e7817238da813e1b914e1cce397eb80a823d53e154a58" }, "downloads": -1, "filename": "purepy-0.8.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "3d2ffcfc2192ffbd25fe7df820f4f344", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7584, "upload_time": "2019-04-17T12:01:18", "url": "https://files.pythonhosted.org/packages/59/ce/c8e45f8abf8f83157f268a6b10c5408215af86e7f5cbfe5db01e6861047a/purepy-0.8.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "637b42e8aa70f6ef9200f017c4b6dcc0", "sha256": "06f0b93c7519172ace448891b5f1c47d6a2f630c7ab1f0302f918a29a25800ef" }, "downloads": -1, "filename": "purepy-0.8.0.tar.gz", "has_sig": false, "md5_digest": "637b42e8aa70f6ef9200f017c4b6dcc0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7358, "upload_time": "2019-04-17T12:01:20", "url": "https://files.pythonhosted.org/packages/e6/b1/61f46f032d57f93c84fc8401e2e3156139231f352bf3463d3be0f0e8897e/purepy-0.8.0.tar.gz" } ], "0.8.1": [ { "comment_text": "", "digests": { "md5": "32b66413adc053ae8120b401f7bbe676", "sha256": "3bfefe7b6b4a15f214e09f8e860611c45ea71ef86e63cd1e4d636d1f22c4aa99" }, "downloads": -1, "filename": "purepy-0.8.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "32b66413adc053ae8120b401f7bbe676", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7624, "upload_time": "2019-04-17T12:27:58", "url": "https://files.pythonhosted.org/packages/2f/96/1b71587f4b722f1b4f84362d6eeb501573129c074f1b90c00f11c092a23e/purepy-0.8.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "740f7b4dfc0447aa6fd3137a7b84bee1", "sha256": "0605efcfcd1e63820a363d149dd2fdb5ba5797876895ba090c92cf073afab380" }, "downloads": -1, "filename": "purepy-0.8.1.tar.gz", "has_sig": false, "md5_digest": "740f7b4dfc0447aa6fd3137a7b84bee1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7412, "upload_time": "2019-04-17T12:27:59", "url": "https://files.pythonhosted.org/packages/c1/27/8cd5869efe476ad48149b0f9a9635fa9a9ccf54aa979bac10ea867bdce7d/purepy-0.8.1.tar.gz" } ], "0.8.2": [ { "comment_text": "", "digests": { "md5": "1ee944cbe879228eff8fa1348d188c6b", "sha256": "68251906c1007003e3abe7e5bdfd0d83bb4255886e2e15e9eb94a63937077a17" }, "downloads": -1, "filename": "purepy-0.8.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "1ee944cbe879228eff8fa1348d188c6b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 9087, "upload_time": "2019-04-18T16:11:22", "url": "https://files.pythonhosted.org/packages/5e/18/0e370ec2c9cf39ec3a82cf92fbcd2047f0f179779495c7718a44eaaf57bb/purepy-0.8.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "311a1553e6ce76949852c899181f07be", "sha256": "46ed526d1633a181fe7973d266e36800e75686ea620928a9dee789cf49d94827" }, "downloads": -1, "filename": "purepy-0.8.2.tar.gz", "has_sig": false, "md5_digest": "311a1553e6ce76949852c899181f07be", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9630, "upload_time": "2019-04-18T16:11:23", "url": "https://files.pythonhosted.org/packages/e7/cf/22fed9239b67d01a70542278c3f7d0871a5c8265734d0d445c64016ff901/purepy-0.8.2.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "f3ab617a14a28efd57b0b907acc30d2b", "sha256": "45c7e60691e88f892a9a0f1c40d55980dc2d6c3ab18457ce215fb177372cd009" }, "downloads": -1, "filename": "purepy-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f3ab617a14a28efd57b0b907acc30d2b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 9536, "upload_time": "2019-04-26T15:22:42", "url": "https://files.pythonhosted.org/packages/bd/c1/f966981a7bc6e915b43e05abce42b913762afbcd0ea8d3a237a55bc7112d/purepy-1.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "259209571bd1d807db35fe922cda8027", "sha256": "9bd81b7cdced7416d134ca0dc0aac136537afd38d1c7848b3f46cdb63dbad523" }, "downloads": -1, "filename": "purepy-1.0.0.tar.gz", "has_sig": false, "md5_digest": "259209571bd1d807db35fe922cda8027", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10153, "upload_time": "2019-04-26T15:22:44", "url": "https://files.pythonhosted.org/packages/f1/e3/cabca2bb3ca49a1ceaa347e0fc7c7e4309c116ad8acc6675658d3b0c3623/purepy-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f3ab617a14a28efd57b0b907acc30d2b", "sha256": "45c7e60691e88f892a9a0f1c40d55980dc2d6c3ab18457ce215fb177372cd009" }, "downloads": -1, "filename": "purepy-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f3ab617a14a28efd57b0b907acc30d2b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 9536, "upload_time": "2019-04-26T15:22:42", "url": "https://files.pythonhosted.org/packages/bd/c1/f966981a7bc6e915b43e05abce42b913762afbcd0ea8d3a237a55bc7112d/purepy-1.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "259209571bd1d807db35fe922cda8027", "sha256": "9bd81b7cdced7416d134ca0dc0aac136537afd38d1c7848b3f46cdb63dbad523" }, "downloads": -1, "filename": "purepy-1.0.0.tar.gz", "has_sig": false, "md5_digest": "259209571bd1d807db35fe922cda8027", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10153, "upload_time": "2019-04-26T15:22:44", "url": "https://files.pythonhosted.org/packages/f1/e3/cabca2bb3ca49a1ceaa347e0fc7c7e4309c116ad8acc6675658d3b0c3623/purepy-1.0.0.tar.gz" } ] }