Verify that implementations satisfy intent specifications. A pure verifier for AI-generated Python code — it checks, it never generates.
Project description
pyintent
Write your intent as a specification. Let any AI coding tool write the implementation. pyintent verifies the implementation actually satisfies the intent.
pyintent is a pure verifier. It never calls an LLM. Like mypy checks types without generating code, pyintent checks that an implementation matches its declared intent — examples, pre/post-conditions, effects, and types.
from pyintent import spec, reads, throws
@spec(
intent = "Return the order with the given id from the database.",
where = ["order_id > 0"],
ensures = ["result.id == order_id"],
effects = [reads("db"), throws(NotFoundError)],
ex = [
"(42,) -> _",
"(999,) -> raises NotFoundError",
"(0,) -> raises ValueError",
],
)
def find_order(order_id: int) -> Order:
... # implemented by Claude Code / Copilot / Devin / you
Then:
pyintent verify myapp/orders.py # run all verifiers, human-readable report
pytest --pyintent # specs become pytest items automatically
Install
pip install pyintent # core (examples, properties, effects)
pip install pyintent[types] # + mypy integration
pip install pyintent[dev] # + mypy + pytest-asyncio
Quick start
from pyintent import spec, pure
@spec(
intent = "Return the absolute value of x.",
effects = [pure],
ensures = ["result >= 0", "result == x or result == -x"],
ex = ["(3,) -> 3", "(-4,) -> 4", "(0,) -> 0"],
)
def my_abs(x: int) -> int:
return x if x >= 0 else -x
$ pyintent verify mymodule.py
[PASS] examples my_abs (3,) -> 3
[PASS] examples my_abs (-4,) -> 4
[PASS] examples my_abs (0,) -> 0
[PASS] properties my_abs
[PASS] effects my_abs pure
[PASS] types mymodule.py
3 passed 0 failed 0 errored 0 skipped
The @spec decorator
@spec accepts these fields:
| Field | Type | Description |
|---|---|---|
intent |
str (required) |
One-line description of what the function does and why. |
where |
list[str] |
Preconditions — Python expressions that must hold over the inputs. |
ensures |
list[str] |
Postconditions — Python expressions over inputs and result. |
effects |
list[Effect] |
Declared side-effects (see below). |
ex |
list[str] |
Runnable examples in "(args) -> expected" format. |
perf |
Perf |
Advisory complexity, e.g. Perf(time="O(n)"). |
invariants |
list[str] |
Class/module-level invariants (plain strings or expressions). |
@spec must be the outermost decorator and returns the target unchanged — it only attaches metadata, so there is zero runtime overhead.
Verifiers
examples — run concrete cases
Each ex string has the format "(args) -> expected":
ex = [
"(1, 2) -> 3", # must return 3
"(0,) -> raises ValueError", # must raise ValueError
"('hi',) -> _", # wildcard: any return without raising
]
- The left side is a tuple literal (single-arg tuples need a trailing comma:
(42,)). raises ExcTypematches if the call raises that type or a subclass._matches any non-raising return.- Values are evaluated in the module's global namespace, so domain objects and enums resolve correctly.
properties — hypothesis-based postcondition testing
For functions with ensures and no impure effects, pyintent generates inputs from type hints using Hypothesis, filters them through where, and asserts every ensures expression:
@spec(
intent = "Sort a list of integers in ascending order.",
effects = [pure],
where = ["len(xs) < 1000"],
ensures = [
"len(result) == len(xs)",
"all(result[i] <= result[i+1] for i in range(len(result)-1))",
],
)
def sort_ints(xs: list[int]) -> list[int]:
return sorted(xs)
ensures expressions may reference input parameters and result (the return value).
types — mypy integration
Runs mypy over the target file. Skipped gracefully if mypy is not installed. Install it with pip install pyintent[types].
effects — AST-based effect checking
Three effects are actively verified in v0.1:
| Effect | What is checked |
|---|---|
pure |
No calls to impure builtins (print, open, …) or modules (os, sys, random, requests, …), no global/nonlocal writes. |
async_ |
The function must be defined with async def. |
throws(ExcA, ExcB) |
Every explicitly raised exception type is declared. |
These effects are declaration-only (recorded but not verified):
reads("db"), writes("cache"), network("stripe"), io
A function may combine multiple effects:
effects = [reads("db"), throws(NotFoundError, ValueError)]
CLI usage
# Write the spec-authoring guide into your AI tool's prompt files
# (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, etc.)
pyintent init
# Print the spec-authoring guide to stdout
pyintent prompt
# Validate spec structure by importing files (no execution)
pyintent check myapp/
# Require every public function to have a @spec
pyintent check --require-specs myapp/
# Run all verifiers and report results
pyintent verify myapp/orders.py
pyintent verify myapp/
# Machine-readable JSON output
pyintent verify --json myapp/ > results.json
# Run only specific verifiers
pyintent verify --only examples --only properties myapp/
Exit codes: 0 all good, 1 verification failures, 2 usage or load error.
pytest plugin
The pytest plugin is opt-in — installing pyintent does not change how existing pytest runs behave.
Enable it on the command line:
pytest --pyintent
Or permanently in pyproject.toml:
[tool.pytest.ini_options]
pyintent = true
Each spec becomes one or more pytest items:
- One item per
excase - One item for property testing (if
ensuresis set) - One item for the type check per file
pyproject.toml configuration
[tool.pyintent]
require_specs = true # or "all" to also require class/module specs
exclude = ["migrations", "tests"]
Safety
pyintent's examples and properties verifiers execute the code under test in the current Python process. That is fine for your own code — but pyintent's whole premise is checking code written by an AI tool, so treat that code as untrusted: review it, or run pyintent verify in a sandbox (container, VM, or restricted user), before running it on your machine.
Status
v0.1. The following are planned for v0.2: generator/async-generator specs, @overload, instance-method example execution, Liskov enforcement of abstract-method contracts, performance measurement.
Contributing
See CONTRIBUTING.md.
License
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 pyintent-0.1.0.tar.gz.
File metadata
- Download URL: pyintent-0.1.0.tar.gz
- Upload date:
- Size: 35.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ebdf3b7a0c4afa08aeb5b26d54357855c320359704e6252fcf611240e4561dc
|
|
| MD5 |
784d7bd601301d4e473c9904634c8cfb
|
|
| BLAKE2b-256 |
5298f775dfd128121509ea8197caebbb940c986b8801d194734578a6f021174c
|
File details
Details for the file pyintent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyintent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4e29ca6f8d45b281867f09c1b7df3f10b679c4174bd8107329ec1d8bc64433f
|
|
| MD5 |
1e34caf47078e62b42d9a70d43593e1d
|
|
| BLAKE2b-256 |
cfc69d81df8e7009cd993cbaf9e338fc5e64dafb79183724965c3ff961326a52
|