{ "info": { "author": "Peter Varo", "author_email": "hello@petervaro.com", "bugtrack_url": null, "classifiers": [], "description": "|pipeline status|\n\n.. image:: img/logo.png?raw=true\n :alt: pcd\n\nPython Contract Decorators\n==========================\n\n- `Abstract <#abstract>`__\n- `Install <#install>`__\n- `Documentation <#documentation>`__\n- `Performance <#performance>`__\n- `Testing <#testing>`__\n- `License <#license>`__\n\nAbstract\n--------\n\nContract programming can boost performance, increase self-documentation coverage\nand all in all is a useful code *hardening* technique. The main principle is\nthat all components in a program should *agree* on how they are interacting with\neach other. Therefore instead of having isolated components which all have to\nguarantee their own correctness, with contract programming entire blocks of\ncomponents guarantee their correctness as a unit.\n\nThough Python is not supporting contracts natively as a language feature, there\nare several libraries out there trying to add the same functionality to Python.\nSadly they are either broken, or incomplete, or extremely heavy, or simply just\nreinventing the wheel by introducing foreign syntax to Python. Fortunately there\nis almost always a light and easy way to do things in Python, and that is\nexactly what ``pcd`` is offering.\n\nAnyway, enough of the abstract *mumbo-jumbo*, let\u2019s start talking about how it\nworks, shall we? As usual, it is easier to understand what is going on via dead\nsimple and dummy examples.\n\nSo, let\u2019s say we have the following setup:\n\n.. code:: python\n\n def get_user_input():\n input = {}\n while True:\n try:\n key, value = raw_input(', or : ').split(',')\n input[key] = value\n except ValueError:\n break\n return process_input(input)\n\n def process_input(input):\n cleaned = {}\n for key, value in input.items():\n cleaned[clean_value(key)] = clean_value(value)\n return cleaned\n\n def clean_value(value):\n return value.strip()\n\nIf we want to make this safer, and the components more reusable for other\ncomponents, the main approach would be hardening each component by introducing\nerror handling in each function, like so:\n\n.. code:: python\n\n def get_user_input():\n input = {}\n while True:\n try:\n key, value = raw_input(', or : ').split(',')\n input[key] = value\n except ValueError:\n break\n # If something went wrong processing inputs\n try:\n return process_input(input)\n except TypeError:\n return {}\n\n def process_input(input):\n cleaned = {}\n # If input is not a dict-like object\n try:\n for key, value in input.items():\n try:\n cleaned[clean_value(key)] = clean_value(value)\n except TypeError:\n continue\n except AttributeError:\n raise TypeError('Invalid input type')\n return cleaned\n\n def clean_value(value):\n # If value is not an str-like object\n try:\n return value.strip()\n except AttributeError:\n raise TypeError('Invalid value type')\n\nNow, this approach has two downsides. On one hand, the code is now cluttered,\nbecause all the error checkings are spread across the functions, making it\nharder to understand what the exact problems each of the functions are trying to\nsolve. On the other hand the introduced error handling mechanism causes\nunnecessary overhead (and sometimes even redundant checkings in seemingly\nunrelated places), that is, the checks are running regardless of the correctness\nof the input data which may already have been checked.\n\nThis is where contract programming comes in! If we can make sure, that the\ntop-level component which is using the other two components can guarentee the\ncorrectness of the inputs, it is completely unnecessary to introduce the above\nshown error handling:\n\n.. code:: python\n\n from pcd import contract\n\n @contract(post=lambda r: isinstance(r, dict))\n def get_user_input():\n input = {}\n while True:\n try:\n key, value = raw_input(', or : ').split(',')\n input[key] = value\n except ValueError:\n break\n return process_input(input)\n\n @contract(pre=lambda: isinstance(input, dict),\n post=lambda r: isinstance(r, dict))\n def process_input(input):\n cleaned = {}\n for key, value in input.items():\n cleaned[clean_value(key)] = clean_value(value)\n return cleaned\n\n @contract(pre=lambda: isinstance(value, str) or\n isinstance(value, unicode))\n def clean_value(value):\n return value.strip()\n\nThe result is much cleaner, easier to read and understand, and best of all at\nthe same time it is also conditionally there, and can be removed without\ntouching the code again. So, after heavily testing the program with the\ncontracts enabled, the application can be optimised greatly by stripping the\ndecorators out. During the development phase, if another component wants to use\nan already *contracted* one then that component has to respect its contracts,\nwhich is exactly what the decorator ensures.\n\nBut that\u2019s not all! Contract programming can be used with *classes*. As a matter\nof fact, it can be used very efficiently to take care of data integrity, which\nis one of the most important aspects of using user defined types. Of course one\ncan use the above explained contracts on selected methods of a class, but\n``pcd`` offers an even better by implementing *invariants*.\n\nBy using invariants, one is automatically generating contracts that are called\nafter the ``__init__``, before the ``__del__``, and before and after every\npublic method and every ``property`` of a class. They are inherited by\nsubclasses, and they can be combined with manually defined contracts as well.\n\nSo, let\u2019s look at an example:\n\n.. code:: python\n\n class Triangle:\n\n def __init__(self, a, b, c):\n if a <= 0 or b <= 0 or c <=0:\n raise ValueError('All sides need to be greater than 0')\n elif a + b <= c or a + c <= b or b + c <= a:\n raise ValueError(\n 'The sum of any two sides has to be greater than the third one')\n self.a = a\n self.b = b\n self.c = c\n\n @property\n def area(self):\n pass # Here goes the implementation of Heron's Formula\n\nFor a seasoned developer it is obvious how dangerious it can be to define (and\nthen use) *public* side names. Because eventhough we made sure during the\ninitialisation, that all sides have valid sizes \u2014 therefore we can safely call\nthe ``area`` method on it \u2014 unfortunately we cannot guarantee the data\nintegrity, as anyone can assign ``-1`` freely to any of the sides. Therefore one\nis tempted to rewrite the above as follows:\n\n.. code:: python\n\n class Triangle:\n\n def __init__(self, a, b, c):\n self._validate(a, b, c)\n self._a = a\n self._b = b\n self._c = c\n\n @staticmethod\n def _validate(a, b, c):\n if a <= 0 or b <= 0 or c <=0:\n raise ValueError('All sides need to be greater than 0')\n elif a + b <= c or a + c <= b or b + c <= a:\n raise ValueError(\n 'Sum of any two sides have to be greather than the third one')\n\n @property\n def a(self):\n return self._a\n @a.setter\n def a(self, value):\n self._validate(value, self._b, self._c)\n self._a = value\n\n @property\n def b(self):\n return self._b\n @b.setter\n def b(self, value):\n self._validate(self._a, value, self._c)\n self._b = value\n\n @property\n def c(self):\n return self._c\n @a.setter\n def c(self, value):\n self._validate(self._a, self._b, value)\n self._c = value\n\n @property\n def area(self):\n pass # Here goes the implementation of Heron's Formula\n\nAlthough this approach is pretty solid (if fellow developers are not overriding\nvariables marked as private by convention) it is very verbose, and the error\nchecking is spread across the entire class, not to mention, that it is also\nmixed with the logic of the program. And not only that, but if ``Triangle`` is\nonly used internally, that is, the values are not coming from user inputs, but\nfrom a trusted source, the checks are running redundantly anyway, creating quite\nan overhead on every assignment.\n\nLet\u2019s see how this example would look like with *invariants*:\n\n.. code:: python\n\n class Triangle:\n\n __metaclass__ = Invariant\n __conditions = (lambda: self.a > 0,\n lambda: self.b > 0,\n lambda: self.c > 0,\n lambda: self.a + self.b > self.c,\n lambda: self.a + self.c > self.b,\n lambda: self.b + self.c > self.a)\n\n def __init__(self, a, b, c):\n self.a = a\n self.b = b\n self.c = c\n\n @property\n def area(self):\n pass # Here goes the implementation of Heron's Formula\n\nAll the conditions defined for ``Triangle`` will run after the call of the\n``__init__`` method, and before and after the ``area`` method. Which means, we\ncan accidentally override a side to an invalid value, but next time we want to\nuse that value in a method, it will be checked immediately. If that is\nconsidered to be too much of a risk, one can also define the getters and setters\nfor each of the values and the validators will be generated for those as well.\nAnd guess what? Yup, the *invariants* can be turned on or off anytime without\nchanging the code. And even more! They are inherited as well!\n\n For further info on contract programming, read this\n `Wikipedia `__\n article.\n\nInstall\n-------\n\n.. code:: bash\n\n pip install pcd\n\nAfter the package has been installed import and use it:\n\n::\n\n from pcd import contract\n\nFor development, clone the `git\nrepository `__ and install the\nrequirements:\n\n.. code:: bash\n\n pip install -r requirements_dev.txt\n\nTo run the tests use ``pytest``:\n\n.. code:: bash\n\n pytest tests.py\n\nDocumentation\n-------------\n\nThe ``pcd`` module defines the following functions and classes:\n\n.. raw:: html\n\n
contract(pre=[callable or iterable of callables],\n            post=[callable or iterable of callables],\n            mut=[callable or iterable of callables])
\n\nThe ``pre`` should contain all the *preconditions* of the decorated function.\nEach *callable* takes no argument, and can use the same argument names that are\ndefined by the decorated function. Every *callable* see all arguments.\n\nThe ``post`` should contain all the *postconditions* of the decorated function.\nEach *callable* takes one argument which can be named freely. This argument will\ncontain the value returned by the decorated function. Every *callable* see all\nof the arguments of the decorated functions as well.\n\nThe ``mut`` should contain all the *postconditions* of the *mutable* arguments.\nThis can be very useful in case the decorated function has side effects via its\narguments. Each *callable* takes no argument, and can use the same argument\nnames that are defined by the decorated function. The checks are called after\nthe function returned. Every *callable* see all arguments.\n\nIf ``__debug__`` is ``True`` then ``contract`` has no effect.\n\n--------------\n\n.. raw:: html\n\n
Invariant()
\n\nThe ``Invariant`` class can be used as a *metaclass*. If that is happening, then\nthe ``__conditions`` of that class will be automatically executed after the\n``__init__`` method, before the ``__del__`` method, and before and after every\npublic method and ``property`` invocations. Each of the conditions can get\naccess to the ``self`` variable.\n\n--------------\n\nRunning the program in a *regular* fashion causes the ``contract`` and\n``Invariant`` to kick in. To remove the checks, run the program with\noptimisations:\n\n.. code:: bash\n\n $ python -O sample.py\n\n--------------\n\n **WARNING!** One should never change the arguments or the return value of\n the decorated function inside the conditions of the ``contract``, as those\n may be mutable values, therefore removing the contracts will alter the\n behaviour of the function and may lead to unexpected behaviour! The same\n thing goes for the invariant conditions, one shall never mutate anything\n inside these callbacks!\n\nPerformance\n-----------\n\nInvoking a function with or without the ``contract`` decorator by running python\nwith the ``-O`` (optimisation) flag has asbolutely no performance penalty. The\nexamples in the ``perf.py`` shows that both functions have the same amount of\nbytecode instructions and their execution times are the same as well.\n\nRunning these functions with simple ``assert``\\ s instead while ``__debug__`` is\n``True`` is course faster than any other execution due to the argument handling\nand injection that is done by the ``contract`` decorator. However doing so makes\nit hard in most cases to check the return value and/or side effects of the\ndecorated function, and ``contract`` is a convenient way of doing that.\n\nTesting\n-------\n\nContract programming plays nicely with unit testing. As a matter of fact it is\nhighly recommended to test the contracts, and the generic behaviour of the code\ncomponent as one would do anyway:\n\n.. code:: python\n\n from pcd import contract\n from pytest import raises\n\n @contract(pre=lambda: len(name) > 0)\n def store_name(name):\n #\n # Normalise and store value ...\n #\n\n # Return the length of the actual value being stored\n return stored_length\n\n def test_store_name():\n # Check constraints of the contract\n if __debug__:\n with raises(AssertionError):\n store_name('')\n\n # Check regular behaviour on correct data\n assert srore_name('hello') == 5\n\nLicense\n-------\n\nCopyright \u00a9 2017 `Peter Varo `__\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Lesser General Public License as published by the Free\nSoftware Foundation, either version 3 of the License, or (at your option) any\nlater version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY\nWARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.\n\nYou should have received a copy of the GNU Lesser General Public License along\nwith this program. If not, see http://www.gnu.org/licenses\n\n--------------\n\nCopyright \u00a9 2017 `Peter Varo `__\n\nThe logo is licensed under a `Creative Commons Attribution-ShareAlike\n4.0 International\nLicense `__.\n\n|license|\n\n.. |pipeline status| image:: https://gitlab.com/petervaro/pcd/badges/master/pipeline.svg\n :target: https://gitlab.com/petervaro/pcd/commits/master\n.. |license| image:: https://i.creativecommons.org/l/by-sa/4.0/80x15.png\n :target: https://creativecommons.org/licenses/by-sa/4.0", "description_content_type": null, "docs_url": null, "download_url": "https://github.com/petervaro/pcd/archive/0.2.2.tar.gz", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/petervaro/pcd", "keywords": "contract,design,testing", "license": "LGPL-3.0", "maintainer": "", "maintainer_email": "", "name": "pcd", "package_url": "https://pypi.org/project/pcd/", "platform": "", "project_url": "https://pypi.org/project/pcd/", "project_urls": { "Download": "https://github.com/petervaro/pcd/archive/0.2.2.tar.gz", "Homepage": "https://github.com/petervaro/pcd" }, "release_url": "https://pypi.org/project/pcd/0.2.2/", "requires_dist": null, "requires_python": "", "summary": "Python Contract Decorators", "version": "0.2.2" }, "last_serial": 3374261, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "fa74e07ebf68f9b8a1a001f26d435f79", "sha256": "bbe3593679f69c9397fb2a7460a049f7842cd3c0b76891ebd5e38f5ebb85e1b1" }, "downloads": -1, "filename": "pcd-0.1.1.tar.gz", "has_sig": true, "md5_digest": "fa74e07ebf68f9b8a1a001f26d435f79", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6249, "upload_time": "2017-11-12T11:56:22", "url": "https://files.pythonhosted.org/packages/75/b1/284c1b846e95d7c38b04ec06981ac1331aee39bb648ab73a9e08c99ed14a/pcd-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "012f6ab04dcdfd2d4d7ac37fc6955957", "sha256": "12dc49f33e2adc0551ca5b0fbdd00d39178b6d3c21a3129c43413e83fd0e0e78" }, "downloads": -1, "filename": "pcd-0.1.2.tar.gz", "has_sig": true, "md5_digest": "012f6ab04dcdfd2d4d7ac37fc6955957", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34980, "upload_time": "2017-11-12T12:19:48", "url": "https://files.pythonhosted.org/packages/05/6a/eb40e94a57be04bf36ca52f57fb8fdd29d16198e2f4479442eb32a0283d6/pcd-0.1.2.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "34ad2ed4892d0bef415e1354e29bed5b", "sha256": "c20d55f073b83a6a2467c4cca7716dfdeb683b153ad7f7d855876a2b90956463" }, "downloads": -1, "filename": "pcd-0.2.1.tar.gz", "has_sig": true, "md5_digest": "34ad2ed4892d0bef415e1354e29bed5b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38004, "upload_time": "2017-11-29T08:45:54", "url": "https://files.pythonhosted.org/packages/24/97/dade1a71c767ff837aff8b385cbe726d363f8132afd3cce5278ff28e9e23/pcd-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "939dc3b51260d576931cdfb07ae56b1f", "sha256": "445fe6914b9947426e5aaa85060dd358da5f3a9d026d33a0d0cae41dad5a0f9b" }, "downloads": -1, "filename": "pcd-0.2.2.tar.gz", "has_sig": true, "md5_digest": "939dc3b51260d576931cdfb07ae56b1f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41787, "upload_time": "2017-11-29T14:19:52", "url": "https://files.pythonhosted.org/packages/ef/b0/fb649342427b1243601da295df278697c60859ee0d957c633fca09c60f24/pcd-0.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "939dc3b51260d576931cdfb07ae56b1f", "sha256": "445fe6914b9947426e5aaa85060dd358da5f3a9d026d33a0d0cae41dad5a0f9b" }, "downloads": -1, "filename": "pcd-0.2.2.tar.gz", "has_sig": true, "md5_digest": "939dc3b51260d576931cdfb07ae56b1f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41787, "upload_time": "2017-11-29T14:19:52", "url": "https://files.pythonhosted.org/packages/ef/b0/fb649342427b1243601da295df278697c60859ee0d957c633fca09c60f24/pcd-0.2.2.tar.gz" } ] }