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
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
OpenStack Hacking Style Checks. Mirror of code maintained at opendev.org.

Introduction hacking is a set of flake8 plugins that test and enforce the OpenStack StyleGuide Hacking pins its dependencies, as a new release of some

Mirrors of opendev.org/openstack 224 Jan 05, 2023
Automated security testing using bandit and flake8.

flake8-bandit Automated security testing built right into your workflow! You already use flake8 to lint all your code for errors, ensure docstrings ar

Tyler Wince 96 Jan 01, 2023
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
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
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
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
Mypy plugin and stubs for SQLAlchemy

Pythonista Stubs Stubs for the Pythonista iOS API. This allows for better error detection and IDE / editor autocomplete. Installation and Usage pip in

Dropbox 521 Dec 29, 2022
Tool for pinpointing circular imports in Python. Find cyclic imports in any project

Pycycle: Find and fix circular imports in python projects Pycycle is an experimental project that aims to help python developers fix their circular de

Vadim Kravcenko 311 Dec 15, 2022
A plugin for Flake8 that provides specializations for type hinting stub files

flake8-pyi A plugin for Flake8 that provides specializations for type hinting stub files, especially interesting for linting typeshed. Functionality A

Łukasz Langa 58 Jan 04, 2023
A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle.

flake8-bugbear A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycode

Python Code Quality Authority 869 Dec 30, 2022
Flake8 plugin to validate annotations complexity

flake8-annotations-complexity An extension for flake8 to report on too complex type annotations. Complex type annotations often means bad annotations

BestDoctor 41 Dec 28, 2022
Flake8 plugin that checks import order against various Python Style Guides

flake8-import-order A flake8 and Pylama plugin that checks the ordering of your imports. It does not check anything else about the imports. Merely tha

Python Code Quality Authority 270 Nov 24, 2022
Flashcards - A flash card application with 2 optional command line arguments

Flashcards A flash card application with 2 optional command line arguments impor

Özgür Yildirim 2 Jul 15, 2022
Flake8 wrapper to make it nice, legacy-friendly, configurable.

THE PROJECT IS ARCHIVED Forks: https://github.com/orsinium/forks It's a Flake8 wrapper to make it cool. Lint md, rst, ipynb, and more. Shareable and r

Life4 232 Dec 16, 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
Run isort, pyupgrade, mypy, pylint, flake8, and more on Jupyter Notebooks

Run isort, pyupgrade, mypy, pylint, flake8, mdformat, black, blacken-docs, and more on Jupyter Notebooks ✅ handles IPython magics robustly ✅ respects

663 Jan 08, 2023
Static Typing for Python

Python static typing home. Contains the source for typing_extensions and the documentation. Also hosts a user help forum.

Python 1.3k Jan 06, 2023
Unbearably fast O(1) runtime type-checking in pure Python.

Look for the bare necessities, the simple bare necessities. Forget about your worries and your strife. — The Jungle Book.

beartype 1.4k Jan 01, 2023
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