Metadata-Version: 2.4
Name: contractme
Version: 1.8.0
Summary: A powerful, expressive and lightweight design-by-contract framework
Project-URL: Repository, https://gitlab.com/leogermond/contractme
Author-email: Leo Germond <leo.germond@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.13
Requires-Dist: annotated-types>=0.7.0
Requires-Dist: hypothesis>=6.131.9
Provides-Extra: cron
Requires-Dist: croniter>=2.0.0; extra == 'cron'
Provides-Extra: iso
Requires-Dist: pycountry>=23.12.11; extra == 'iso'
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# ContractMe

[![pipeline status](https://gitlab.com/leogermond/contractme/badges/main/pipeline.svg)](https://gitlab.com/leogermond/contractme/-/commits/main) 
![coverage](https://gitlab.com/leogermond/contractme/badges/main/coverage.svg?job=checks)
[![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Code style: flake8](https://img.shields.io/badge/code%20style-flake8-000000.svg)](https://github.com/PyCQA/flake8)

A lightweight and adaptable framework for design-by-contract in python

# Example code

Here are some examples:

## `result`

```python
@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`

```python
@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

```python
@annotated
def incr(v : int) -> int:
    return v + 1
```

Supports annotations and [PEP-593](https://peps.python.org/pep-0593/)
using the [annotated-types](https://pypi.org/project/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.

**Batteries included:** the `contractme.types` package ships ~140 ready-made
`Annotated` types (`Port`, `ExistingFile`, `EmailStr`, `Positive`, `Slug`, …).
See the practical guide: [docs/types.md](docs/types.md).

**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.

```python
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.

```python
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](https://hypothesis.readthedocs.io/en/latest/reference/api.html)

```python
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 `*args` or dict `**kwarg` as examples, to avoid
  function nesting.

With pytest:
```python
test_div = contractme.testing.test_with_examples(
    div,
    (1,),
    (2,),
    (0,), # this causes a RuntimeError at test elaboration
)
```

# Optimize assertion code

- In prod you can disable assertions, then these runtime checks wont run: you can have your cake
and eat it too
- You have even more granularity of checks thanks to
`contractme.contracting.ignore_preconditions` and `contractme.contracting.ignore_postconditions`

In **theory**, the rule is that checks are activated depending on the trust you put in your software

- In dev: You run with all assertions, to catch as many errors as possible, as early
as possible
- In pre-deploy / integration testing: you only run with the pre-conditions assertions:
postconditions are typically costly, and you trust that you return the right result given the proper input
- In prod: you run with no assertion - those are meant for debugging, not user facing failure modes which
are / should be handled properly in sanitization code

In practice, you might want to keep then on all of the time, but being able to turn them off means one
smart thing: you can get overboard in checking with postcondition, knowing these can be turned off in
integration conditions
e.g. you can check that a database insert succeeded by following it with a select - not that you necessarily
should, ToCToU and all that.

# Test

`uv run pytest`

# Deploy new version

* Write changelog in `README.md`
* Increase version in `pyproject.toml`
* `uv build`
* Push the resulting new lock file
* Git tag as `v<number>`
* Gitlab will take care of doing the release

# Changelog

## v1.8.0

* New `contractme.types` package: a large, batteries-included library of ready-made
  `Annotated` types (numeric, text, paths, network, temporal, identifiers, system,
  structured-data, containers) usable out of the box with `@annotated` and pydantic
* Names follow Ada/SPARK (`Positive`, `Natural`) and pydantic where applicable, with
  aliases pointing at the same objects
* Reusable named predicates in `contractme.types.predicates` (+ `PredicateFn` /
  `PredicateFactory` typing aliases)
* Factories `Bounded[lo, hi]` and `SuffixPath[".csv", ...]`, and a `SecretStr` wrapper
* Optional extras for richer validators: `cron` (croniter), `iso` (pycountry),
  `yaml` (pyyaml) — lazily imported, not required to `import contractme.types`
* `typecheck`: nested `Annotated` constraints (e.g. `Annotated[str, LowerCase]`) are now
  flattened, so `annotated-types` predicate aliases compose naturally
* New practical guide: [docs/types.md](docs/types.md)

## v1.7.0

* Refactored lambda source extraction (`show_source`) for improved robustness when stripping surrounding parentheses
* Added docstrings to public API modules for better discoverability
* Added CLAUDE.md with architecture guidance for AI-assisted development
* `typecheck`: identifies types with no true origin in recursive type resolution
* Fix unreachable test code detected by coverage

## v1.6.0

* Fix huge bug where instance methods were not working anymore
* Add support for class methods
* Richer types stubs for pre / post (still very imperfect)

## v1.5.1

* Minor contracted functions fix

## v1.5.0

* Contracted functions have a correct return type.

## 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
