Skip to content
Design by contract for Python with many validators support.
Python Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
deal
docs
tests
.editorconfig
.gitignore improve typing a little bit Oct 4, 2019
.travis.yml
LICENSE
MANIFEST.in
README.md Updates readme with the correct links Oct 6, 2019
deploy.sh
logo.png
logo.svg
pyproject.toml
requirements-flake.txt
setup.cfg

README.md

Deal

Build Status Coverage Status PyPI version Development Status Code size

Deal -- python library for design by contract (DbC) programming.

That's nice assert statements in decorators style to validate function input, output, available operations and object state. Goal is make testing much easier and detect errors in your code that occasionally was missed in tests.

Features

  • Functional declaration.
  • Custom exceptions.
  • Raising exceptions from contract.
  • Django Forms styled validators.
  • Attribute setting invariant validation.
  • Dynamically assigned attributes and methods invariant validation.
  • Decorators to control available resources: forbid input/output, network operations, raising exceptions

Available decorators

CLassic DbC:

  • @deal.pre -- validate function arguments (pre-condition)
  • @deal.post -- validate function return value (post-condition)
  • @deal.inv -- validate object internal state (invariant)

Take more control:

  • @deal.offline -- forbid network requests
  • @deal.raises -- allow only list of exceptions
  • @deal.safe -- forbid exceptions
  • @deal.silent -- forbid output into stderr/stdout.

Installation

pip3 install --user deal

Quick Start

import re

import attr
import deal

REX_LOGIN = re.compile(r'^[a-zA-Z][a-zA-Z0-9]+$')

class PostAlreadyLiked(Exception):
    pass

@deal.inv(lambda post: post.visits >= 0)
class Post:
    visits: int = attr.ib(default=0)
    likes: set = attr.ib(factory=set)

    @deal.pre(lambda user: REX_LOGIN.match(user), message='invalid username format')
    @deal.raises(PostAlreadyLiked)
    @deal.chain(deal.offline, deal.silent)
    def like(self, user: str) -> None:
        if user in self.likes:
            raise PostAlreadyLiked
        self.likes.add(user)

    @deal.post(lambda result: 'visits' in result)
    @deal.post(lambda result: 'likes' in result)
    @deal.post(lambda result: result['likes'] > 0)
    @deal.pure
    def get_state(self):
        return dict(visits=self.visits, likes=len(self.likes))

Now, Deal controls conditions and states of the object at runtime:

  1. @deal.inv controls that visits count in post always non-negative.
  2. @deal.pre checks user name format. We assume that it should be validated somewhere before by some nice forms with user-friendly error messages. So, if we have invalid login passed here, it's definitely developer's mistake.
  3. @deal.raises says that only possible exception that can be raised is PostAlreadyLiked.
  4. @deal.chain(deal.offline, deal.silent) controls that function has no network requests and has no output in stderr or stdout. So, if we are making unexpected network requests somewhere inside, deal let us know about it.
  5. deal.post checks result format for get_state. So, all external code can be sure that fields likes and visits always represented in the result and likes always positive.

If code violates some condition, sub-exception of deal.ContractError will be raised:

p = Post()
p.visits = -1
# InvContractError:

Dive deeper on deal.readthedocs.io.

You can’t perform that action at this time.