A powerful, expressive and lightweight design-by-contract framework
Project description
ContractMe
A lightweight and adaptable framework for design-by-contract in python
Example code
Here are some examples:
result
@precondition(lambda x: x >= 0)
@postcondition(lambda x, result: eps_eq(result * result, x))
def square_root(x: float) -> float:
return x**0.5
old
@precondition(lambda l, n: n >= 0 and round(n) == n)
@postcondition(lambda l, n: len(l) > 0)
@postcondition(lambda l, n: l[-1] == n)
@postcondition(lambda l, n, old: l[:-1] == old.l)
def append_count(l: list[int], n: int):
l.append(n)
Using annotations
@annotated
def incr(v : int) -> int:
return v + 1
Supports annotations and PEP-593
using the annotated-types library.
Furthermore, the @annotated decorator will automatically perform type checks
of the parameters and return values, including annotated_types.Predicate.
In short, this allows to check any type structure and any properties of all parameters
and the return value, by just adding @annotated to the subprogram.
Note: annodated_types.MultipleOf follows the Python semantics.
Note 2: Following an open-world reasoning, any unknown annotation is considered to be correct, so it won't cause a check failure.
Note 3: Type checking follows Python's isinstance semantics, which means subclass
relationships are respected. Since bool is a subclass of int in Python, boolean values
will pass int type checks. Currently there's no built-in way to specify "exactly int, not bool"
in type annotations.
from typing import TypeAlias, Annotated
from annotated_types import MultipleOf
Even: TypeAlias = Annotated[int, MultipleOf(2)]
@annotated
def square(v : Even) -> Even
return v * v
Writing tests and having test generation
The hypothesis plugin can be used easily through the contractme.testing.autotest
function.
Positive: TypeAlias = Annotated[int, Ge(1)]
@annotated
def div(d: Positive) return Positive:
return 1000 // d
def test_div():
autotest(div)
You can access the underlying hypothesis generator with contractme.testing.get_generator(div).
It's a pure hypothesis strategy generator, inferred from the annotated types and contracts of the function. The main weirdness is that it takes a tuple as parameter since the parameters are all generated together so that the contracts can be checked.
You can easily extend it with Hypothesis advanced features
generator_function = contractme.testing.get_generator(div)
# kinda weird to have this double call, but that's decorators for you...
test_div_force_0 = example((0,))(generator_function)
The library provides its own contractme.testing.test_with_examples function which has three differences
with the one provided by hypothesis:
- It checks the contracts when being called (at test construction): contracts should hold on all examples.
- It takes a vararg of either tuple
*argsor dict**kwargas examples, to avoid function nesting.
With pytest:
test_div = contractme.testing.test_with_examples(
div,
(1,),
(2,),
(0,), # this causes a RuntimeError at test elaboration
)
Test
uv run pytest
Deploy new version
uv build- Push the resulting new lock file
- Git tag as
v<number> - Gitlab will take care of doing the release
Changelog
v1.4.0
@annotated supports more complex types
- TypeAlias
- Recursive types
@annotated supports common nested data types
- tuple
- set
- list
- union
- dict
@annotated UX improvement: Split between structural and constraint checks.
Minor: Update dev dependencies and reorder CI a bit
v1.3.0
Binding and helpers to hypothesis library for test data generation.
v1.2.0
Full support of annotated-types library for checking PEP-593 compatible type annotations
automatically through the @annotated decorator.
Generated contracted functions are now of a ContractedFunction class, with a original_call
attribute that contains the function without contracts checking.
Pyright check for the totality of the code.
v1.1.0
Contracts can be disabled at runtime with ignore_preconditions() and ignore_postconditions()
Contracts are disabled from the start with python optimized (-O) flag.
Fix a bug where contracts would hide an incorrect function call
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file contractme-1.4.0.tar.gz.
File metadata
- Download URL: contractme-1.4.0.tar.gz
- Upload date:
- Size: 27.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98647cd00c78a3d16b76d86d3b3526131da3e4603b64cf240c1029bde964143c
|
|
| MD5 |
c926792ea47eb7a07689c95f62a49b95
|
|
| BLAKE2b-256 |
6beea844a6385bf8e9a4c1443b115fa6b5cc22111d2d9f5bd2cf95c9eddacb4b
|
File details
Details for the file contractme-1.4.0-py3-none-any.whl.
File metadata
- Download URL: contractme-1.4.0-py3-none-any.whl
- Upload date:
- Size: 15.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e613dbd8de7b72d925b103ccafeab751495da08cdc8617c17aaf4d2d4f93a634
|
|
| MD5 |
363778f5ae011110c1337f5bbc3bb766
|
|
| BLAKE2b-256 |
a80cb60499d70e7cc744babc6e117d8acf06e06912979ddac7272de54182fa0a
|