Skip to content

Commit

Permalink
+recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
orsinium committed Apr 20, 2020
1 parent fa757c8 commit c79a1a5
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 9 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ That's nice `assert` statements in decorators style to validate function input,

CLassic DbC:

* [`@deal.pre`](https://deal.readthedocs.io/decorators/pre.html) -- validate function arguments (pre-condition)
* [`@deal.post`](https://deal.readthedocs.io/decorators/post.html) -- validate function return value (post-condition)
* [`@deal.ensure`](https://deal.readthedocs.io/decorators/ensure.html) -- post-condition that accepts not only result, but also function arguments.
* [`@deal.inv`](https://deal.readthedocs.io/decorators/inv.html) -- validate object internal state (invariant).
* [deal.pre](https://deal.readthedocs.io/decorators/pre.html) -- validate function arguments (pre-condition)
* [deal.post](https://deal.readthedocs.io/decorators/post.html) -- validate function return value (post-condition)
* [deal.ensure](https://deal.readthedocs.io/decorators/ensure.html) -- post-condition that accepts not only result, but also function arguments.
* [deal.inv](https://deal.readthedocs.io/decorators/inv.html) -- validate object internal state (invariant).

Take more control:

* [`@deal.module_load`](https://deal.readthedocs.io/decorators/module_load.html) -- check contracts at module initialization.
* [`@deal.offline`](https://deal.readthedocs.io/decorators/offline.html) -- forbid network requests
* [`@deal.raises`](https://deal.readthedocs.io/decorators/raises.html) -- allow only list of exceptions
* [`@deal.reason`](https://deal.readthedocs.io/decorators/reason.html) -- check function arguments that caused a given exception.
* [`@deal.silent`](https://deal.readthedocs.io/decorators/silent.html) -- forbid output into stderr/stdout.
* [deal.module_load](https://deal.readthedocs.io/decorators/module_load.html) -- check contracts at module initialization.
* [deal.offline](https://deal.readthedocs.io/decorators/offline.html) -- forbid network requests
* [deal.raises](https://deal.readthedocs.io/decorators/raises.html) -- allow only list of exceptions
* [deal.reason](https://deal.readthedocs.io/decorators/reason.html) -- check function arguments that caused a given exception.
* [deal.silent](https://deal.readthedocs.io/decorators/silent.html) -- forbid output into stderr/stdout.

Helpers:

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
validators
testing
linter
recipes
.. toctree::
:maxdepth: 1
Expand Down
65 changes: 65 additions & 0 deletions docs/recipes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Recipes

Some ideas that are useful in the real world applications.

## Keep contracts simple

If a function accepts only a few short arguments, duplicate the original signature (without annotations) for contracts:

```python
@deal.pre(lambda left, right: right != 0)
def div(left: float, right: float) -> float:
return left / right
```

Otherwise, or if a function has default arguments, use simplified signature for contracts:

```python
@deal.pre(lambda _: _.default is not None or _.right != 0)
def div(left: float, right: float, default: float = None) -> float:
try:
return left / right
except ZeroDivisionError:
if default is not None:
return default
raise
```

## Type checks

Never check types with deal. [Mypy](https://github.com/python/mypy) does it much better. Also, there are [plenty of alternatives](https://github.com/typeddjango/awesome-python-typing) for both static and dynamic validation. Deal is intended to empower types, say a bit more about possible values set than you can do with type annotations, not replace them. However, if you want to play with deal a bit or make types a part of contracts, [PySchemes](https://github.com/spy16/pyschemes)-based contract is the best choice:

```python
import deal
from pyschemes import Scheme

@deal.pre(Scheme(dict(left=str, right=str)))
def concat(left, right):
return left + right

concat('ab', 'cd')
# 'abcd'

concat(1, 2)
# PreContractError: at key 'left' (expected type: 'str', got 'int')
```

## Prefer `pre` and `post` over `ensure`

If a contract needs only function arguments, use `pre`. If a contract checks only function result, use `post`. And only if a contract need both input and output values at the same time, use `ensure`. Keeping available namespace for contract as small as possible makes the contract signature simpler and helps with partial execution in the linter.

## Prefer `reason` over `raises`

Always try your best to tell why exception can be raised.

## Keep module initialization pure

Nothing should happen on module load. Create some constants, compile regexpes, and that's all. Make it lazy.

```python
deal.module_load(deal.pure)
```

## Contract shouldn't be important

Never catch contract errors. Never rely on them in runtime. They are for tests and humans. The shouldn't have an actual logic, only validate it.

0 comments on commit c79a1a5

Please sign in to comment.