Introduction

What’s a contract?

Design-by-contract programming is an approach by which you define the requirements or expectations of your code before it executes. So when some external code calls your own code, it’s expected that the former meets the requirements of the latter. In other words, it must respect the established contract. Otherwise, an error is raised.

So let’s say that you have the following function:

1
2
def build_rocket(name, model, company):
    print("You built a {0} {1} rocket from {2}.".format(name, model, company))

You’d normally do what follows to impose requirements on its parameters:

1
2
3
4
5
6
7
8
9
def build_rocket(name, model, company):
    if not name:
        raise ValueError("name should not be empty")
    if model <= 0:
        raise ValueError("model should be greater than 0")
    if not company:
        raise ValueError("company should not be empty")

    print("You built a {0} {1} rocket from {2}.".format(name, model, company))

So any time one of those requirements isn’t met, it raises a ValueError.

Now, this may be quite handy to prevent potential bugs. And this may work quite well for a single script that contains only a few functions. But:

  • Do you see yourself repeating those manual checks everywhere in a much larger program?
  • What if you’d like those checks to cover a little bit more ground, like supporting more data types or performing more complex validations?

Then you’d most likely need to create a collection of well-tested contracts, so that you can reuse them whenever needed. Enters the code-contracts library!

Here’s how using contracts greatly simplifies the previous example:

1
2
3
4
5
6
def build_rocket(name, model, company):
    contract.is_not_empty(name)
    contract.is_greater_than(model, 0)
    contract.is_not_empty(company)

    print("You built a {0} {1} rocket from {2}.".format(name, model, company))

Why a new library?

A search for “contract” in Pypi leads to many design-by-contract libraries for Python, the most popular being PyContracts and Augment. These are very good and reasonable choices.

So why create another library?

Well, because I, the author, am not completely satisfied with what exists out there. Many of those libraries:

  1. Impose contracts to be written as decorators - which, arguably, might lead people to view them as optional or as an afterthought, while they’re really an essential part of your code.
  2. Impose constraints to be written as strings, or as pretty complex lambda expressions - which makes debugging harder, and which encourages copy-paste.
  3. Raise their own custom exceptions, instead of raising built-in ones like ValueError or TypeError.
  4. Mix up the concepts of contract and assertion, while they’re normally not intended to be used interchangeably.
  5. Have fairly limited documentation, or none at all.
  6. Have a fairly limiting license, like some flavor of GPL.
  7. Only perform type checking.

Additionally, there were some early efforts by the Python community to make contracts officially part of the language, but the idea was apparently abandoned.

Finally - major spoiler alert - code-contracts is above all else an experiment to learn more about packaging, deploying and documenting open-source software (OSS).

What about assertions?

Assertions are similar to contracts, in that they impose requirements on your code. But they also differ in some important ways:

  • They’re generally used for debugging purposes only.
  • They’re often used in unit tests to validate results.

So assertions and contracts are definitely complementary to each other.

Python already provides many assertions in the TestCase class, but a few important ones are missing. For example, you can validate that a function raises an error using assertRaises(), but there’s nothing like assertNotRaises or anything equivalent, surprisingly.

This is why code-contracts provides a few of those useful, but missing assertions, in addition to contracts.

Here’s an example using does_not_raise():

1
2
3
4
5
6
7
import unittest
from contracts import assertion


class RocketTests(unittest.TestCase):
    def test_build_rocket(self):
        assertion.does_not_raise(ValueError, build_rocket, "Falcon", 9, "SpaceX")

Limitations

code-contracts has been battle-tested via unit testing and heavy use across several projects, so it’s fairly stable.

But don’t expect it to be a full-fledged library, as it only provides a few functions to fill the gaps of existing libraries. Remember that code-contracts is essentially an experiment with OSS, as stated previously.

Finally, right now, you can’t use contract functions outside of a function or method - this is as designed. Note that assertion functions don’t have the same limitation, though.

License

code-contracts is released under the terms of the Apache License Version 2.0:

Copyright 2017 Benoit Bernard

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

The current documentation is released under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.