Package: pcd
|pipeline status|.. image:: img/logo.png?raw=true:alt: pcdPython Contract Decorators==========================-  `Abstract <#abstract>`__-  `Install <#install>`__-  `Documentation <#documentation>`__-  `Performance <#performance>`__-  `Testing <#testing>`__-  `License <#license>`__Abstract--------Contract programming can boost performance, increase self-documentation coverageand all in all is a useful code *hardening* technique. The main principle isthat all components in a program should *agree* on how they are interacting witheach other. Therefore instead of having isolated components which all have toguarantee their own correctness, with contract programming entire blocks ofcomponents guarantee their correctness as a unit.Though Python is not supporting contracts natively as a language feature, thereare several libraries out there trying to add the same functionality to Python.Sadly they are either broken, or incomplete, or extremely heavy, or simply justreinventing the wheel by introducing foreign syntax to Python. Fortunately thereis almost always a light and easy way to do things in Python, and that isexactly what ``pcd`` is offering.Anyway, enough of the abstract *mumbo-jumbo*, let’s start talking about how itworks, shall we? As usual, it is easier to understand what is going on via deadsimple and dummy examples.So, let’s say we have the following setup:.. code:: pythondef get_user_input():input = {}while True:try:key, value = raw_input('<key>,<value> or <return>: ').split(',')input[key] = valueexcept ValueError:breakreturn process_input(input)def process_input(input):cleaned = {}for key, value in input.items():cleaned[clean_value(key)] = clean_value(value)return cleaneddef clean_value(value):return value.strip()If we want to make this safer, and the components more reusable for othercomponents, the main approach would be hardening each component by introducingerror handling in each function, like so:.. code:: pythondef get_user_input():input = {}while True:try:key, value = raw_input('<key>,<value> or <return>: ').split(',')input[key] = valueexcept ValueError:break# If something went wrong processing inputstry:return process_input(input)except TypeError:return {}def process_input(input):cleaned = {}# If input is not a dict-like objecttry:for key, value in input.items():try:cleaned[clean_value(key)] = clean_value(value)except TypeError:continueexcept AttributeError:raise TypeError('Invalid input type')return cleaneddef clean_value(value):# If value is not an str-like objecttry:return value.strip()except AttributeError:raise TypeError('Invalid value type')Now, this approach has two downsides. On one hand, the code is now cluttered,because all the error checkings are spread across the functions, making itharder to understand what the exact problems each of the functions are trying tosolve. On the other hand the introduced error handling mechanism causesunnecessary overhead (and sometimes even redundant checkings in seeminglyunrelated places), that is, the checks are running regardless of the correctnessof the input data which may already have been checked.This is where contract programming comes in! If we can make sure, that thetop-level component which is using the other two components can guarentee thecorrectness of the inputs, it is completely unnecessary to introduce the aboveshown error handling:.. code:: pythonfrom pcd import contract@contract(post=lambda r: isinstance(r, dict))def get_user_input():input = {}while True:try:key, value = raw_input('<key>,<value> or <return>: ').split(',')input[key] = valueexcept ValueError:breakreturn process_input(input)@contract(pre=lambda: isinstance(input, dict),post=lambda r: isinstance(r, dict))def process_input(input):cleaned = {}for key, value in input.items():cleaned[clean_value(key)] = clean_value(value)return cleaned@contract(pre=lambda: isinstance(value, str) orisinstance(value, unicode))def clean_value(value):return value.strip()The result is much cleaner, easier to read and understand, and best of all atthe same time it is also conditionally there, and can be removed withouttouching the code again. So, after heavily testing the program with thecontracts enabled, the application can be optimised greatly by stripping thedecorators out. During the development phase, if another component wants to usean already *contracted* one then that component has to respect its contracts,which is exactly what the decorator ensures.But that’s not all! Contract programming can be used with *classes*. As a matterof fact, it can be used very efficiently to take care of data integrity, whichis one of the most important aspects of using user defined types. Of course onecan use the above explained contracts on selected methods of a class, but``pcd`` offers an even better by implementing *invariants*.By using invariants, one is automatically generating contracts that are calledafter the ``__init__``, before the ``__del__``, and before and after everypublic method and every ``property`` of a class. They are inherited bysubclasses, and they can be combined with manually defined contracts as well.So, let’s look at an example:.. code:: pythonclass Triangle:def __init__(self, a, b, c):if a <= 0 or b <= 0 or c <=0:raise ValueError('All sides need to be greater than 0')elif a + b <= c or a + c <= b or b + c <= a:raise ValueError('The sum of any two sides has to be greater than the third one')self.a = aself.b = bself.c = c@propertydef area(self):pass  # Here goes the implementation of Heron's FormulaFor a seasoned developer it is obvious how dangerious it can be to define (andthen use) *public* side names. Because eventhough we made sure during theinitialisation, that all sides have valid sizes — therefore we can safely callthe ``area`` method on it — unfortunately we cannot guarantee the dataintegrity, as anyone can assign ``-1`` freely to any of the sides. Therefore oneis tempted to rewrite the above as follows:.. code:: pythonclass Triangle:def __init__(self, a, b, c):self._validate(a, b, c)self._a = aself._b = bself._c = c@staticmethoddef _validate(a, b, c):if a <= 0 or b <= 0 or c <=0:raise ValueError('All sides need to be greater than 0')elif a + b <= c or a + c <= b or b + c <= a:raise ValueError('Sum of any two sides have to be greather than the third one')@propertydef a(self):return self._a@a.setterdef a(self, value):self._validate(value, self._b, self._c)self._a = value@propertydef b(self):return self._b@b.setterdef b(self, value):self._validate(self._a, value, self._c)self._b = value@propertydef c(self):return self._c@a.setterdef c(self, value):self._validate(self._a, self._b, value)self._c = value@propertydef area(self):pass  # Here goes the implementation of Heron's FormulaAlthough this approach is pretty solid (if fellow developers are not overridingvariables marked as private by convention) it is very verbose, and the errorchecking is spread across the entire class, not to mention, that it is alsomixed with the logic of the program. And not only that, but if ``Triangle`` isonly used internally, that is, the values are not coming from user inputs, butfrom a trusted source, the checks are running redundantly anyway, creating quitean overhead on every assignment.Let’s see how this example would look like with *invariants*:.. code:: pythonclass Triangle:__metaclass__ = Invariant__conditions  = (lambda: self.a > 0,lambda: self.b > 0,lambda: self.c > 0,lambda: self.a + self.b > self.c,lambda: self.a + self.c > self.b,lambda: self.b + self.c > self.a)def __init__(self, a, b, c):self.a = aself.b = bself.c = c@propertydef area(self):pass  # Here goes the implementation of Heron's FormulaAll the conditions defined for ``Triangle`` will run after the call of the``__init__`` method, and before and after the ``area`` method. Which means, wecan accidentally override a side to an invalid value, but next time we want touse that value in a method, it will be checked immediately. If that isconsidered to be too much of a risk, one can also define the getters and settersfor each of the values and the validators will be generated for those as well.And guess what? Yup, the *invariants* can be turned on or off anytime withoutchanging the code. And even more! They are inherited as well!For further info on contract programming, read this`Wikipedia <https://en.wikipedia.org/wiki/Design_by_contract>`__article.Install-------.. code:: bashpip install pcdAfter the package has been installed import and use it:::from pcd import contractFor development, clone the `gitrepository <https://gitlab.com/petervaro/pcd>`__ and install therequirements:.. code:: bashpip install -r requirements_dev.txtTo run the tests use ``pytest``:.. code:: bashpytest tests.pyDocumentation-------------The ``pcd`` module defines the following functions and classes:.. raw:: html<pre><code><b>contract</b><i>(</i><b>pre</b>=[<i>callable</i> or <i>iterable of callables</i>],<b>post</b>=[<i>callable</i> or <i>iterable of callables</i>],<b>mut</b>=[<i>callable</i> or <i>iterable of callables</i>]<i>)</i></code></pre>The ``pre`` should contain all the *preconditions* of the decorated function.Each *callable* takes no argument, and can use the same argument names that aredefined by the decorated function. Every *callable* see all arguments.The ``post`` should contain all the *postconditions* of the decorated function.Each *callable* takes one argument which can be named freely. This argument willcontain the value returned by the decorated function. Every *callable* see allof the arguments of the decorated functions as well.The ``mut`` should contain all the *postconditions* of the *mutable* arguments.This can be very useful in case the decorated function has side effects via itsarguments. Each *callable* takes no argument, and can use the same argumentnames that are defined by the decorated function. The checks are called afterthe function returned. Every *callable* see all arguments.If ``__debug__`` is ``True`` then ``contract`` has no effect.--------------.. raw:: html<pre><code><b>Invariant</b><i>()</i></code></pre>The ``Invariant`` class can be used as a *metaclass*. If that is happening, thenthe ``__conditions`` of that class will be automatically executed after the``__init__`` method, before the ``__del__`` method, and before and after everypublic method and ``property`` invocations. Each of the conditions can getaccess to the ``self`` variable.--------------Running the program in a *regular* fashion causes the ``contract`` and``Invariant`` to kick in. To remove the checks, run the program withoptimisations:.. code:: bash$ python -O sample.py--------------**WARNING!** One should never change the arguments or the return value ofthe decorated function inside the conditions of the ``contract``, as thosemay be mutable values, therefore removing the contracts will alter thebehaviour of the function and may lead to unexpected behaviour! The samething goes for the invariant conditions, one shall never mutate anythinginside these callbacks!Performance-----------Invoking a function with or without the ``contract`` decorator by running pythonwith the ``-O`` (optimisation) flag has asbolutely no performance penalty. Theexamples in the ``perf.py`` shows that both functions have the same amount ofbytecode instructions and their execution times are the same as well.Running these functions with simple ``assert``\ s instead while ``__debug__`` is``True`` is course faster than any other execution due to the argument handlingand injection that is done by the ``contract`` decorator. However doing so makesit hard in most cases to check the return value and/or side effects of thedecorated function, and ``contract`` is a convenient way of doing that.Testing-------Contract programming plays nicely with unit testing. As a matter of fact it ishighly recommended to test the contracts, and the generic behaviour of the codecomponent as one would do anyway:.. code:: pythonfrom pcd import contractfrom pytest import raises@contract(pre=lambda: len(name) > 0)def store_name(name):## Normalise and store value ...## Return the length of the actual value being storedreturn stored_lengthdef test_store_name():# Check constraints of the contractif __debug__:with raises(AssertionError):store_name('')# Check regular behaviour on correct dataassert srore_name('hello') == 5License-------Copyright © 2017 `Peter Varo <http://www.petervaro.com>`__This program is free software: you can redistribute it and/or modify it underthe terms of the GNU Lesser General Public License as published by the FreeSoftware Foundation, either version 3 of the License, or (at your option) anylater version.This program is distributed in the hope that it will be useful, but WITHOUT ANYWARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR APARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.You should have received a copy of the GNU Lesser General Public License alongwith this program. If not, see http://www.gnu.org/licenses--------------Copyright © 2017 `Peter Varo <http://www.petervaro.com>`__The logo is licensed under a `Creative Commons Attribution-ShareAlike4.0 InternationalLicense <https://creativecommons.org/licenses/by-sa/4.0>`__.|license|.. |pipeline status| image:: https://gitlab.com/petervaro/pcd/badges/master/pipeline.svg:target: https://gitlab.com/petervaro/pcd/commits/master.. |license| image:: https://i.creativecommons.org/l/by-sa/4.0/80x15.png:target: https://creativecommons.org/licenses/by-sa/4.0