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
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
Mypy stubs for the PyQt5 framework

Mypy stubs for the PyQt5 framework This repository holds the stubs of the PyQt5 framework. It uses the stub files that are produced during compilation

62 Nov 22, 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
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
Tool to automatically fix some issues reported by flake8 (forked from autoflake).

autoflake8 Introduction autoflake8 removes unused imports and unused variables from Python code. It makes use of pyflakes to do this. autoflake8 also

francisco souza 27 Sep 08, 2022
👻 Phantom types for Python

phantom-types Phantom types for Python will help you make illegal states unrepresentable and avoid shotgun parsing by enabling you to practice "Parse,

Anton Agestam 118 Dec 22, 2022
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
❄️ A flake8 plugin to help you write better list/set/dict comprehensions.

flake8-comprehensions A flake8 plugin that helps you write better list/set/dict comprehensions. Requirements Python 3.6 to 3.9 supported. Installation

Adam Johnson 398 Dec 23, 2022
Tools for improving Python imports

imptools Tools for improving Python imports. Installation pip3 install imptools Overview Detailed docs import_path Import a module from any path on th

Danijar Hafner 7 Aug 07, 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
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 python documentation linter which checks that the docstring description matches the definition.

Darglint A functional docstring linter which checks whether a docstring's description matches the actual function/method implementation. Darglint expe

Terrence Reilly 463 Dec 31, 2022
Flake8 extension to provide force-check option

flake8-force Flake8 extension to provide force-check option. When this option is enabled, flake8 performs all checks even if the target file cannot be

Kenichi Maehashi 9 Oct 29, 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
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
Reference implementation of sentinels for the Python stdlib

Sentinels This is a reference implementation of a utility for the definition of sentinel values in Python. This also includes a draft PEP for the incl

Tal Einat 22 Aug 27, 2022
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
It's not just a linter that annoys you!

README for Pylint - https://pylint.pycqa.org/ Professional support for pylint is available as part of the Tidelift Subscription. Tidelift gives softwa

Python Code Quality Authority 4.4k Jan 04, 2023
Backport Python 3.8+ typing utils & add issubtype & more

typing-utils Backport Python3.8+ typing utils & issubtype & more Install API issubtype get_origin get_args get_type_hints Install pip install typi

10 Nov 09, 2022
Stubs with type annotations for ordered-set Python library

ordered-set-stubs - stubs with type annotations for ordered-set Python library Archived - now type annotations are the part of the ordered-set library

Roman Inflianskas 2 Feb 06, 2020