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
Tool for automatically reordering python imports. Similar to isort but uses static analysis more.

reorder_python_imports Tool for automatically reordering python imports. Similar to isort but uses static analysis more. Installation pip install reor

Anthony Sottile 589 Dec 26, 2022
A simple plugin that allows running mypy from PyCharm and navigate between errors

mypy-PyCharm-plugin The plugin provides a simple terminal to run fast mypy daemon from PyCharm with a single click or hotkey and easily navigate throu

Dropbox 301 Dec 09, 2022
Performant type-checking for python.

Pyre is a performant type checker for Python compliant with PEP 484. Pyre can analyze codebases with millions of lines of code incrementally – providi

Facebook 6.2k Jan 04, 2023
Rust like Option and Result types in Python

Option Rust-like Option and Result types in Python, slotted and fully typed. An Option type represents an optional value, every Option is either Some

45 Dec 13, 2022
Type stubs for the lxml package

lxml-stubs About This repository contains external type annotations (see PEP 484) for the lxml package. Installation To use these stubs with mypy, you

25 Dec 26, 2022
An enhanced version of the Python typing library.

typingplus An enhanced version of the Python typing library that always uses the latest version of typing available, regardless of which version of Py

Contains 6 Mar 26, 2021
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
The strictest and most opinionated python linter ever!

wemake-python-styleguide Welcome to the strictest and most opinionated python linter ever. wemake-python-styleguide is actually a flake8 plugin with s

wemake.services 2.1k Jan 01, 2023
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 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
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
Utilities for refactoring imports in python-like syntax.

aspy.refactor_imports Utilities for refactoring imports in python-like syntax. Installation pip install aspy.refactor_imports Examples aspy.refactor_i

Anthony Sottile 20 Nov 01, 2022
Tool to check the completeness of MANIFEST.in for Python packages

check-manifest Are you a Python developer? Have you uploaded packages to the Python Package Index? Have you accidentally uploaded broken packages with

Marius Gedminas 270 Dec 26, 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
Design by contract for Python. Write bug-free code. Add a few decorators, get static analysis and tests for free.

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 co

Life4 473 Dec 28, 2022
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
A framework for detecting, highlighting and correcting grammatical errors on natural language text.

Gramformer Human and machine generated text often suffer from grammatical and/or typographical errors. It can be spelling, punctuation, grammatical or

Prithivida 1.3k Jan 08, 2023
The official GitHub mirror of https://gitlab.com/pycqa/flake8

Flake8 Flake8 is a wrapper around these tools: PyFlakes pycodestyle Ned Batchelder's McCabe script Flake8 runs all the tools by launching the single f

Python Code Quality Authority 2.6k Jan 03, 2023
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
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