Design by contract for Python. Write bug-free code. Add a few decorators, get static analysis and tests for free.

Overview

Deal

Build Status PyPI version Development Status

A Python library for design by contract (DbC) and checking values, exceptions, and side-effects. In a nutshell, deal empowers you to write bug-free code. By adding a few decorators to your code, you get for free tests, static analysis, formal verification, and much more. Read intro to get started.

Features

Deal in 30 seconds

# the result is always non-negative
@deal.post(lambda result: result >= 0)
# the function has no side-effects
@deal.pure
def count(items: List[str], item: str) -> int:
    return items.count(item)

# generate test function
test_count = deal.cases(count)

Now we can:

  • Run python3 -m deal lint or flake8 to statically check errors.
  • Run python3 -m deal test or pytest to generate and run tests.
  • Just use the function in the project and check errors in runtime.

Read more in the documentation.

Installation

python3 -m pip install --user deal

Contributing

Contributions are welcome! A few ideas what you can contribute:

  • Add new checks for the linter.
  • Improve documentation.
  • Add more tests.
  • Improve performance.
  • Found a bug? Fix it!
  • Made an article about deal? Great! Let's add it into the README.md.
  • Don't have time to code? No worries! Just tell your friends and subscribers about the project. More users -> more contributors -> more cool features.

Thank you ❤️

Comments
  • Check for _.result in 'ensure' contract linting

    Check for _.result in 'ensure' contract linting

    Previously, using _ in a @deal.ensure contract validator would lead to this error:

    $ python3 -m deal lint
    dumbpw/pwgen.py
      32:4 DEAL002 ensure contract must have `result` arg
        lambda _: len(_.result) == _.length,
        ^
    

    This change fixes that false positive by detecting when the arg name is _ and passing the lint check if _.result is anywhere in the validator body.

    opened by rpdelaney 10
  • Add special handling for missing deal_solver

    Add special handling for missing deal_solver

    Before this change, the prover attempts to handle the case where deal_solver cannot be imported by setting deal_solver = None. However, instantiation of the DealTheorem class a few lines later depends on deal_solver. This results in an unhandled exception because None does not have a "Theorem" attribute:

    $ python3 -m deal lint
    Traceback (most recent call last):
      File "/Users/ryan/.local/share/asdf/installs/python/3.10.2/lib/python3.10/runpy.py", line 196, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "/Users/ryan/.local/share/asdf/installs/python/3.10.2/lib/python3.10/runpy.py", line 86, in _run_code
        exec(code, run_globals)
      File "/Users/ryan/src/me/extinfo/.venv/lib/python3.10/site-packages/deal/__main__.py", line 6, in <module>
        sys.exit(main(sys.argv[1:]))
      File "/Users/ryan/src/me/extinfo/.venv/lib/python3.10/site-packages/deal/_cli/_main.py", line 37, in main
        commands = get_commands()
      File "/Users/ryan/src/me/extinfo/.venv/lib/python3.10/site-packages/deal/_cli/_main.py", line 16, in get_commands
        from ._prove import ProveCommand
      File "/Users/ryan/src/me/extinfo/.venv/lib/python3.10/site-packages/deal/_cli/_prove.py", line 26, in <module>
        class DealTheorem(deal_solver.Theorem):
    AttributeError: 'NoneType' object has no attribute 'Theorem'
    

    After this change, a special exception is raised to get_commands() so that when deal_solver import fails, the ProveCommand can be set to an empty Command (because the prover cannot run at all without the solver).

    opened by rpdelaney 6
  • Fix incompatible type in raises(SystemExit)

    Fix incompatible type in raises(SystemExit)

    The deal linter decorates functions that call sys.exit() with @deal.raises(SystemExit). SystemExit inherits from BaseException, which makes it incompatible with Exception:

    dumbpw/cli.py:14:14: error: Argument 1 to "raises" has incompatible type
    "Type[SystemExit]"; expected "Type[Exception]"  [arg-type]
        @deal.raises(SystemExit)
                     ^
    Found 1 error in 1 file (checked 14 source files)
    

    This change annotates the raises() decorator to expect BaseException so that SystemExit can be included without an incompatible type error from the type checker.

    opened by rpdelaney 5
  • Update stubs.md

    Update stubs.md

    It would be helpful for python3 -m deal stub /path/to/a/file.py to detect whether file.py already has deal annotations and generate itself based on them. This could be a neat way to separate deal contracts into their own file rather than intrude on the code, sort of like it's possible to do with type hints.

    opened by Ayenem 3
  • Make vaa optional

    Make vaa optional

    Most of vaa usage is for short signature. I doubt anyone really uses schemas. So, let's reimplement simple signature logic on deal side and make vaa optional.

    opened by orsinium 1
  • Don't call typeguard if it's not available

    Don't call typeguard if it's not available

    Attempting to run the "deal in 30s" example:

    # the result is always non-negative
    @deal.post(lambda result: result >= 0)
    # the function has no side-effects
    @deal.pure
    def count(items: List[str], item: str) -> int:
        return items.count(item)
    
    # generate test function
    test_count = deal.cases(count)
    

    fails with the following error run under pytest:

    [nix-shell:~/code/nixpkgs]$ pytest deal_test.py
    ======================================================== test session starts ========================================================
    platform darwin -- Python 3.9.11, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/panashe/code/nixpkgs
    plugins: hypothesis-6.38.0
    collected 1 item
    
    deal_test.py F                                                                                                                [100%]
    
    ============================================================= FAILURES ==============================================================
    ____________________________________________________________ test_count _____________________________________________________________
    
    >   ???
    
    /nix/store/3mnlyndr9s9r0v49ynahldczkawqa076-python3.9-deal-4.21.1/lib/python3.9/site-packages/deal/_testing.py:329:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    /nix/store/3mnlyndr9s9r0v49ynahldczkawqa076-python3.9-deal-4.21.1/lib/python3.9/site-packages/deal/_testing.py:328: in <lambda>
        return self._wrap(lambda case: case())
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
    self = TestCase(args=(), kwargs={'items': [], 'item': ''}, func=<function count at 0x10fe74af0>, exceptions=(), check_types=True)
    result = 0
    
        def _check_result(self, result: typing.Any) -> None:
            if not self.check_types:
                return
    >       memo = typeguard._CallMemo(
                func=self.func,
                args=self.args,
                kwargs=self.kwargs,
            )
    E       AttributeError: 'NoneType' object has no attribute '_CallMemo'
    
    /nix/store/3mnlyndr9s9r0v49ynahldczkawqa076-python3.9-deal-4.21.1/lib/python3.9/site-packages/deal/_testing.py:68: AttributeError
    ------------------------------------------------------- Captured stdout call --------------------------------------------------------
    
    You can reproduce this example by temporarily adding @reproduce_failure('6.38.0', b'AAAA') as a decorator on your test case
    ====================================================== short test summary info ======================================================
    FAILED deal_test.py::test_count - AttributeError: 'NoneType' object has no attribute '_CallMemo'
    ========================================================= 1 failed in 0.81s =========================================================
    

    This does not fail if you just call deal.cases(count)(). Looking into the failure, this is attempting to call typeguard when the dependency is not available. In the constructor, self.check_types defaults to True when not passed, but there is no explicit check that typeguard exists before using it, even though it should optional.

    opened by munyari 1
  • Create py.typed

    Create py.typed

    Related #44

    That's the first step to get the typing support. The next step is to run mypy on the source code.

    And probably fix some problems, annotate some missing functions, etc.

    opened by sobolevn 1
Releases(4.23.4)
  • 4.23.4(Sep 1, 2022)

    What's Changed

    • Integration test for flake8 by @orsinium in https://github.com/life4/deal/pull/120
    • fix(flake8): Flake8 does not support 4 letter codes anymore. by @ruler501 in https://github.com/life4/deal/pull/119
    • Detect noqa comments by @orsinium in https://github.com/life4/deal/pull/122

    New Contributors

    • @ruler501 made their first contribution in https://github.com/life4/deal/pull/119

    Full Changelog: https://github.com/life4/deal/compare/4.23.3...4.23.4

    Source code(tar.gz)
    Source code(zip)
  • 4.23.3(May 2, 2022)

    What's Changed

    • linter: detect self even when it is a posonlyarg by @orsinium in https://github.com/life4/deal/pull/116
    • Make vaa optional by @orsinium in https://github.com/life4/deal/pull/117

    Full Changelog: https://github.com/life4/deal/compare/4.23.2...4.23.3

    Source code(tar.gz)
    Source code(zip)
  • 4.23.2(Apr 21, 2022)

    What's Changed

    • Add explicit docs for @deal.safe by @rpdelaney in https://github.com/life4/deal/pull/113
    • linter: detect keyword validator by @orsinium in https://github.com/life4/deal/pull/115
    • Fix incompatible type in raises(SystemExit) by @rpdelaney in https://github.com/life4/deal/pull/114

    Full Changelog: https://github.com/life4/deal/compare/4.23.1...4.23.2

    Source code(tar.gz)
    Source code(zip)
  • 4.23.1(Apr 12, 2022)

    What's Changed

    • Lazy annotations by @orsinium in https://github.com/life4/deal/pull/110
    • Enable Python 3.10 pytest on CI by @orsinium in https://github.com/life4/deal/pull/111
    • Improve import time by @orsinium in https://github.com/life4/deal/pull/112

    Full Changelog: https://github.com/life4/deal/compare/4.23.0...4.23.1

    Source code(tar.gz)
    Source code(zip)
  • 4.23.0(Apr 12, 2022)

    What's Changed

    • Support deal.pure in code generator by @orsinium in https://github.com/life4/deal/pull/109

    Full Changelog: https://github.com/life4/deal/compare/4.22.0...4.23.0

    Source code(tar.gz)
    Source code(zip)
  • 4.22.0(Apr 9, 2022)

    I accidentally released it as 4.21.2 but then realized that it has a feature included, not only a bug fix. So, now you have two releases with the same changes inside.

    What's Changed

    • Don't call typeguard if it's not available by @munyari in https://github.com/life4/deal/pull/108
    • Allow permanently disabling contracts by @orsinium in https://github.com/life4/deal/pull/107

    New Contributors

    • @munyari made their first contribution in https://github.com/life4/deal/pull/108

    Full Changelog: https://github.com/life4/deal/compare/4.21.1...4.21.2

    Source code(tar.gz)
    Source code(zip)
  • 4.21.1(Mar 18, 2022)

    What's Changed

    • Add special handling for missing deal_solver by @rpdelaney in https://github.com/life4/deal/pull/106

    Full Changelog: https://github.com/life4/deal/compare/4.21.0...4.21.1

    Source code(tar.gz)
    Source code(zip)
  • 4.21.0(Mar 18, 2022)

    What's Changed

    • Correct variable reference in code sample by @jgberry in https://github.com/life4/deal/pull/104
    • Linter: extract exceptions from docstrings by @orsinium in https://github.com/life4/deal/pull/105

    New Contributors

    • @jgberry made their first contribution in https://github.com/life4/deal/pull/104

    Full Changelog: https://github.com/life4/deal/compare/4.20.0...4.21.0

    Source code(tar.gz)
    Source code(zip)
  • 4.20.0(Mar 18, 2022)

    What's Changed

    • Make some dependencies optional by @orsinium in https://github.com/life4/deal/pull/103

    Full Changelog: https://github.com/life4/deal/compare/4.19.2...4.20.0

    Source code(tar.gz)
    Source code(zip)
  • 4.19.2(Mar 18, 2022)

    What's Changed

    • Improve linter behavior for assert by @orsinium in https://github.com/life4/deal/pull/102

    Full Changelog: https://github.com/life4/deal/compare/4.19.1...4.19.2

    Source code(tar.gz)
    Source code(zip)
  • 4.19.1(Dec 30, 2021)

    What's Changed

    • Add some more copyedits to docs by @rpdelaney in https://github.com/life4/deal/pull/100
    • Check for _.result in 'ensure' contract linting by @rpdelaney in https://github.com/life4/deal/pull/101

    Full Changelog: https://github.com/life4/deal/compare/4.19.0...4.19.1

    Source code(tar.gz)
    Source code(zip)
  • 4.19.0(Dec 3, 2021)

    What's Changed

    • improve wording and fix typos in README by @jacobszpz in https://github.com/life4/deal/pull/98
    • Copyedits to docs by @rpdelaney in https://github.com/life4/deal/pull/99
    • Lint methods by @orsinium in https://github.com/life4/deal/pull/97

    New Contributors

    • @jacobszpz made their first contribution in https://github.com/life4/deal/pull/98
    • @rpdelaney made their first contribution in https://github.com/life4/deal/pull/99

    Full Changelog: https://github.com/life4/deal/compare/4.18.0...4.19.0

    Source code(tar.gz)
    Source code(zip)
  • 4.18.0(Nov 18, 2021)

    What's Changed

    • Code generation (python3 -m deal decorate CLI command) by @orsinium in https://github.com/life4/deal/pull/96

    Full Changelog: https://github.com/life4/deal/compare/4.17.0...4.18.0

    Source code(tar.gz)
    Source code(zip)
  • 4.17.0(Nov 10, 2021)

    What's Changed

    • Linter: support deal.inherit for methods by @orsinium in https://github.com/life4/deal/pull/95
    • Document CrossHair integration by @orsinium in https://github.com/life4/deal/pull/94

    Full Changelog: https://github.com/life4/deal/compare/4.16.0...4.17.0

    Source code(tar.gz)
    Source code(zip)
  • 4.16.0(Nov 5, 2021)

    What's Changed

    • deal.inherit by @orsinium in https://github.com/life4/deal/pull/93
    • Enable contracts when running @deal.dispatch by @orsinium in https://github.com/life4/deal/pull/92

    Full Changelog: https://github.com/life4/deal/compare/4.15.0...4.16.0

    Source code(tar.gz)
    Source code(zip)
  • 4.15.0(Oct 18, 2021)

    What's Changed

    • Better AST traversing by @orsinium in https://github.com/life4/deal/pull/89
    • Linter: require deal.ensure to have result arg by @orsinium in https://github.com/life4/deal/pull/90
    • deal.dispatch: propagate PreContractError by @orsinium in https://github.com/life4/deal/pull/91

    Full Changelog: https://github.com/life4/deal/compare/4.14.0...4.15.0

    Source code(tar.gz)
    Source code(zip)
  • 4.14.0(Oct 18, 2021)

    What's Changed

    • linter: more markers for deal.has by @orsinium in https://github.com/life4/deal/pull/88

    Full Changelog: https://github.com/life4/deal/compare/4.13.0...4.14.0

    Source code(tar.gz)
    Source code(zip)
  • 4.13.0(Oct 18, 2021)

    What's Changed

    • Rewrite runtime by @orsinium in https://github.com/life4/deal/pull/87

    Full Changelog: https://github.com/life4/deal/compare/4.12.0...4.13.0

    Source code(tar.gz)
    Source code(zip)
  • 4.12.0(Oct 18, 2021)

    What's Changed

    • @deal.example by @orsinium in https://github.com/life4/deal/pull/86

    Full Changelog: https://github.com/life4/deal/compare/4.11.0...4.12.0

    Source code(tar.gz)
    Source code(zip)
  • 4.11.0(Sep 27, 2021)

    What's Changed

    • Migrate from recommonmark to myst-parser by @orsinium in https://github.com/life4/deal/pull/84
    • MyPy plugin by @orsinium in https://github.com/life4/deal/pull/79
    • Much better performance for deal.inv by @orsinium in https://github.com/life4/deal/pull/85

    Full Changelog: https://github.com/life4/deal/compare/4.10.0...4.11.0

    Source code(tar.gz)
    Source code(zip)
  • 4.10.0(Sep 24, 2021)

  • 4.9.0(Sep 23, 2021)

  • 4.8.0(Sep 20, 2021)

  • 4.7.2(Jul 11, 2021)

  • 4.7.1(Jul 11, 2021)

  • 4.7.0(Jul 8, 2021)

Owner
Life4
Original cool Open Source projects
Life4
Mylint - My really simple rendition of how a linter works.

mylint My really simple rendition of how a linter works. This original version was written for my AST article. Since then I've added tests and turned

Tushar Sadhwani 2 Dec 29, 2021
Flake8 extension for checking quotes in python

Flake8 Extension to lint for quotes. Major update in 2.0.0 We automatically encourage avoiding escaping quotes as per PEP 8. To disable this, use --no

Zachary Heller 157 Dec 13, 2022
Flake8 plugin for managing type-checking imports & forward references

flake8-type-checking Lets you know which imports to put in type-checking blocks. For the imports you've already defined inside type-checking blocks, i

snok 67 Dec 16, 2022
Flake8 Type Annotation Checking

flake8-annotations flake8-annotations is a plugin for Flake8 that detects the absence of PEP 3107-style function annotations and PEP 484-style type co

S. Co1 118 Jan 05, 2023
A plugin for Flake8 that checks pandas code

pandas-vet pandas-vet is a plugin for flake8 that provides opinionated linting for pandas code. It began as a project during the PyCascades 2019 sprin

Jacob Deppen 146 Dec 28, 2022
Pylint plugin to enforce some secure coding standards for Python.

Pylint Secure Coding Standard Plugin pylint plugin that enforces some secure coding standards. Installation pip install pylint-secure-coding-standard

Nguyen Damien 2 Jan 04, 2022
docstring style checker

pydocstyle - docstring style checker pydocstyle is a static analysis tool for checking compliance with Python docstring conventions. pydocstyle suppor

Python Code Quality Authority 982 Jan 03, 2023
flake8 plugin that integrates isort

Flake8 meet isort Use isort to check if the imports on your python files are sorted the way you expect. Add an .isort.cfg to define how you want your

Gil Forcada Codinachs 139 Nov 08, 2022
A static type analyzer for Python code

pytype - 🦆 ✔ Pytype checks and infers types for your Python code - without requiring type annotations. Pytype can: Lint plain Python code, flagging c

Google 4k Dec 31, 2022
Utilities for pycharm code formatting (flake8 and black)

Pycharm External Tools Extentions to Pycharm code formatting tools. Currently supported are flake8 and black on a selected code block. Usage Flake8 [P

Haim Daniel 13 Nov 03, 2022
Flake8 plugin to find commented out or dead code

flake8-eradicate flake8 plugin to find commented out (or so called "dead") code. This is quite important for the project in a long run. Based on eradi

wemake.services 277 Dec 27, 2022
The mypy playground. Try mypy with your web browser.

mypy-playground The mypy playground provides Web UI to run mypy in the sandbox: Features Web UI and sandbox for running mypy eas

Yusuke Miyazaki 57 Jan 02, 2023
Plugin for mypy to support zope.interface

Plugin for mypy to support zope.interface The goal is to be able to make zope interfaces to be treated as types in mypy sense. Usage Install both mypy

Shoobx 36 Oct 29, 2022
A simple program which checks Python source files for errors

Pyflakes A simple program which checks Python source files for errors. Pyflakes analyzes programs and detects various errors. It works by parsing the

Python Code Quality Authority 1.2k Dec 30, 2022
Convert relative imports to absolute

absolufy-imports A tool and pre-commit hook to automatically convert relative imports to absolute. Installation $ pip install absolufy-imports Usage a

Marco Gorelli 130 Dec 30, 2022
MyPy types for WSGI applications

WSGI Types for Python This is an attempt to bring some type safety to WSGI applications using Python's new typing features (TypedDicts, Protocols). It

Blake Williams 2 Aug 18, 2021
A static-analysis bot for Github

Imhotep, the peaceful builder. What is it? Imhotep is a tool which will comment on commits coming into your repository and check for syntactic errors

Justin Abrahms 221 Nov 10, 2022
flake8 plugin which checks that typing imports are properly guarded

flake8-typing-imports flake8 plugin which checks that typing imports are properly guarded installation pip install flake8-typing-imports flake8 codes

Anthony Sottile 50 Nov 01, 2022
flake8 plugin to run black for checking Python coding style

flake8-black Introduction This is an MIT licensed flake8 plugin for validating Python code style with the command line code formatting tool black. It

Peter Cock 146 Dec 15, 2022
An open-source, mini imitation of GitHub Copilot for Emacs.

Second Mate An open-source, mini imitation of GitHub Copilot using EleutherAI GPT-Neo-2.7B (via Huggingface Model Hub) for Emacs. This is a much small

Sam Rawal 238 Dec 27, 2022