pyintent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ """Examples verifier — run every ``ex`` case against the real implementation.
2
+
3
+ Runnable in v0.1 for module-level functions, ``@staticmethod`` and
4
+ ``@classmethod``. Instance methods and properties need an instance and are
5
+ skipped with a clear reason (planned for v0.2).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from typing import Any
12
+
13
+ from .._errors import PyIntentSpecError
14
+ from .._parser import Example, Raises, ReturnsValue, _Wildcard
15
+ from .._spec import SpecLevel
16
+ from .._discovery import SpecTarget
17
+ from ._result import CheckResult, Status
18
+
19
+ _RUNNABLE = {SpecLevel.FUNCTION, SpecLevel.STATICMETHOD, SpecLevel.CLASSMETHOD}
20
+
21
+
22
+ def _safe_eq(a: object, b: object) -> bool:
23
+ try:
24
+ return bool(a == b)
25
+ except Exception:
26
+ return repr(a) == repr(b)
27
+
28
+
29
+ def _call(fn: Any, args: tuple, is_async: bool) -> Any:
30
+ if is_async:
31
+ return asyncio.run(fn(*args))
32
+ return fn(*args)
33
+
34
+
35
+ def _fmt_args(args: tuple) -> str:
36
+ inner = ", ".join(repr(a) for a in args)
37
+ if len(args) == 1:
38
+ inner += ","
39
+ return f"({inner})"
40
+
41
+
42
+ def verify_example_case(target: SpecTarget, ex: Example) -> CheckResult:
43
+ """Run exactly one example case (the function executes once)."""
44
+ sp = target.spec
45
+ if sp.level not in _RUNNABLE or target.invoke is None:
46
+ reason = f"{sp.level.value} examples require an instance (v0.2)"
47
+ return CheckResult("examples", sp.target_name, Status.SKIPPED, summary=reason, label=ex.raw)
48
+ return _run_one(target, ex)
49
+
50
+
51
+ def verify_examples(target: SpecTarget) -> list[CheckResult]:
52
+ if not target.spec.examples:
53
+ return []
54
+ return [verify_example_case(target, ex) for ex in target.spec.examples]
55
+
56
+
57
+ def _run_one(target: SpecTarget, ex: Example) -> CheckResult:
58
+ sp = target.spec
59
+ name = sp.target_name
60
+ globalns = target.globalns
61
+ try:
62
+ args = ex.eval_args(globalns)
63
+ except PyIntentSpecError as e:
64
+ return CheckResult("examples", name, Status.ERROR, summary=str(e), label=ex.raw)
65
+
66
+ try:
67
+ outcome = _call(target.invoke, args, sp.is_async)
68
+ except BaseException as exc: # noqa: BLE001 - we classify it below
69
+ return _check_raised(name, ex, exc, args, globalns)
70
+ return _check_returned(name, ex, outcome, args, globalns)
71
+
72
+
73
+ def _check_raised(name, ex: Example, exc, args, globalns) -> CheckResult:
74
+ if isinstance(ex.expected, Raises):
75
+ try:
76
+ want = ex.expected.resolve(globalns)
77
+ except PyIntentSpecError as e:
78
+ return CheckResult("examples", name, Status.ERROR, summary=str(e), label=ex.raw)
79
+ if isinstance(exc, want):
80
+ return CheckResult("examples", name, Status.PASS, label=ex.raw)
81
+ detail = (
82
+ f"{name}{_fmt_args(args)}\n"
83
+ f" expected: raises {ex.expected.exc_name}\n"
84
+ f" actual: raised {type(exc).__name__}: {exc}"
85
+ )
86
+ return CheckResult(
87
+ "examples", name, Status.FAIL,
88
+ summary=f"raised {type(exc).__name__}, expected {ex.expected.exc_name}",
89
+ detail=detail, label=ex.raw,
90
+ )
91
+
92
+ expected_desc = (
93
+ "_ (returns without raising)"
94
+ if isinstance(ex.expected, _Wildcard)
95
+ else ex.expected.value_src
96
+ )
97
+ detail = (
98
+ f"{name}{_fmt_args(args)}\n"
99
+ f" expected: {expected_desc}\n"
100
+ f" actual: raised {type(exc).__name__}: {exc}"
101
+ )
102
+ return CheckResult(
103
+ "examples", name, Status.FAIL,
104
+ summary=f"unexpected {type(exc).__name__}: {exc}",
105
+ detail=detail, label=ex.raw,
106
+ )
107
+
108
+
109
+ def _check_returned(name, ex: Example, outcome, args, globalns) -> CheckResult:
110
+ if isinstance(ex.expected, _Wildcard):
111
+ return CheckResult("examples", name, Status.PASS, label=ex.raw)
112
+
113
+ if isinstance(ex.expected, Raises):
114
+ detail = (
115
+ f"{name}{_fmt_args(args)}\n"
116
+ f" expected: raises {ex.expected.exc_name}\n"
117
+ f" actual: returned {outcome!r}"
118
+ )
119
+ return CheckResult(
120
+ "examples", name, Status.FAIL,
121
+ summary=f"returned {outcome!r}, expected raise {ex.expected.exc_name}",
122
+ detail=detail, label=ex.raw,
123
+ )
124
+
125
+ assert isinstance(ex.expected, ReturnsValue)
126
+ try:
127
+ expected_value = ex.expected.resolve(globalns)
128
+ except PyIntentSpecError as e:
129
+ return CheckResult("examples", name, Status.ERROR, summary=str(e), label=ex.raw)
130
+
131
+ if _safe_eq(outcome, expected_value):
132
+ return CheckResult("examples", name, Status.PASS, label=ex.raw)
133
+ detail = (
134
+ f"{name}{_fmt_args(args)}\n"
135
+ f" expected: {expected_value!r}\n"
136
+ f" actual: {outcome!r}"
137
+ )
138
+ return CheckResult(
139
+ "examples", name, Status.FAIL,
140
+ summary=f"returned {outcome!r}, expected {expected_value!r}",
141
+ detail=detail, label=ex.raw,
142
+ )
@@ -0,0 +1,196 @@
1
+ """Properties verifier — hypothesis-generate inputs and check ``ensures``.
2
+
3
+ Only runs for pure, runnable callables (module functions / staticmethods /
4
+ classmethods with no impure declared effects). Effectful callables, instance
5
+ methods, and callables whose parameter types can't be mapped to a strategy are
6
+ skipped with a reason.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import builtins as _builtins
12
+ import inspect
13
+ import types
14
+ import typing
15
+ from typing import Any, get_args, get_origin
16
+
17
+ _BUILTINS_DICT = vars(_builtins)
18
+
19
+ from hypothesis import HealthCheck, given, settings
20
+ from hypothesis import strategies as st
21
+ from hypothesis.errors import UnsatisfiedAssumption
22
+
23
+ from .._spec import SpecLevel
24
+ from .._discovery import SpecTarget
25
+ from ._result import CheckResult, Status
26
+
27
+ _RUNNABLE = {SpecLevel.FUNCTION, SpecLevel.STATICMETHOD, SpecLevel.CLASSMETHOD}
28
+ _MAX_EXAMPLES = 50
29
+
30
+
31
+ class _Unsupported(Exception):
32
+ def __init__(self, tp: Any) -> None:
33
+ super().__init__(str(tp))
34
+ self.tp = tp
35
+
36
+
37
+ def _strategy_for(tp: Any):
38
+ if tp is int:
39
+ return st.integers()
40
+ if tp is bool:
41
+ return st.booleans()
42
+ if tp is float:
43
+ return st.floats(allow_nan=False, allow_infinity=False)
44
+ if tp is str:
45
+ return st.text()
46
+ if tp is bytes:
47
+ return st.binary()
48
+ if tp is type(None):
49
+ return st.none()
50
+
51
+ origin = get_origin(tp)
52
+ args = get_args(tp)
53
+ if origin is list:
54
+ return st.lists(_strategy_for(args[0]) if args else st.integers())
55
+ if origin is set:
56
+ return st.sets(_strategy_for(args[0]) if args else st.integers())
57
+ if origin is frozenset:
58
+ return st.frozensets(_strategy_for(args[0]) if args else st.integers())
59
+ if origin is tuple:
60
+ if len(args) == 2 and args[1] is Ellipsis:
61
+ return st.lists(_strategy_for(args[0])).map(tuple)
62
+ return st.tuples(*[_strategy_for(a) for a in args])
63
+ if origin is dict:
64
+ k = _strategy_for(args[0]) if args else st.text()
65
+ v = _strategy_for(args[1]) if len(args) > 1 else st.integers()
66
+ return st.dictionaries(k, v)
67
+ if origin is typing.Union or origin is types.UnionType:
68
+ return st.one_of(*[_strategy_for(a) for a in args])
69
+ raise _Unsupported(tp)
70
+
71
+
72
+ def _skip(name: str, reason: str) -> list[CheckResult]:
73
+ return [CheckResult("properties", name, Status.SKIPPED, summary=reason)]
74
+
75
+
76
+ def verify_properties(target: SpecTarget) -> list[CheckResult]:
77
+ sp = target.spec
78
+ name = sp.target_name
79
+
80
+ if not sp.ensures:
81
+ return []
82
+ if sp.level not in _RUNNABLE or target.invoke is None:
83
+ return _skip(name, f"{sp.level.value} property tests need an instance (v0.2)")
84
+ if not sp.is_verifiable_pure:
85
+ return _skip(name, "effectful — property testing only runs on pure functions")
86
+ if sp.is_async:
87
+ return _skip(name, "async property testing deferred to v0.2")
88
+
89
+ fn = target.invoke
90
+ try:
91
+ sig = inspect.signature(fn)
92
+ except (TypeError, ValueError):
93
+ return _skip(name, "could not introspect signature")
94
+
95
+ params = [
96
+ p for p in sig.parameters.values()
97
+ if p.name not in ("self", "cls")
98
+ ]
99
+ for p in params:
100
+ if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
101
+ return _skip(name, "*args/**kwargs not supported for property testing")
102
+ if p.kind is p.POSITIONAL_ONLY:
103
+ return _skip(name, "positional-only parameters not supported")
104
+
105
+ try:
106
+ hints = typing.get_type_hints(fn)
107
+ except Exception:
108
+ return _skip(name, "could not resolve type hints")
109
+
110
+ strategies = {}
111
+ for p in params:
112
+ if p.name not in hints:
113
+ return _skip(name, f"parameter '{p.name}' has no type annotation")
114
+ try:
115
+ strategies[p.name] = _strategy_for(hints[p.name])
116
+ except _Unsupported as u:
117
+ return _skip(name, f"no strategy for type {u.tp!r} (parameter '{p.name}')")
118
+
119
+ return [_run(target, strategies)]
120
+
121
+
122
+ def _run(target: SpecTarget, strategies: dict) -> CheckResult:
123
+ sp = target.spec
124
+ name = sp.target_name
125
+ fn = target.invoke
126
+ assert fn is not None # guaranteed by verify_properties
127
+ globalns = target.globalns
128
+ failure: dict[str, Any] = {}
129
+
130
+ arg_strategy = st.fixed_dictionaries(strategies) if strategies else st.just({})
131
+
132
+ @settings(max_examples=_MAX_EXAMPLES, deadline=None,
133
+ suppress_health_check=list(HealthCheck))
134
+ @given(kwargs=arg_strategy)
135
+ def run(kwargs: dict) -> None:
136
+ for cond in sp.where:
137
+ try:
138
+ ok = bool(eval(cond, {"__builtins__": _BUILTINS_DICT, **globalns, **kwargs}))
139
+ except Exception:
140
+ ok = False
141
+ if not ok:
142
+ raise UnsatisfiedAssumption()
143
+ try:
144
+ result = fn(**kwargs)
145
+ except Exception as exc:
146
+ failure.update(kwargs=kwargs, result="<raised>", cond="(function call)", error=exc)
147
+ raise AssertionError(
148
+ f"function raised {type(exc).__name__}: {exc}"
149
+ ) from exc
150
+ env = {**kwargs, "result": result}
151
+ for cond in sp.ensures:
152
+ try:
153
+ holds = bool(eval(cond, {"__builtins__": _BUILTINS_DICT, **globalns, **env}))
154
+ except Exception as e:
155
+ failure.update(kwargs=kwargs, result=result, cond=cond, error=e)
156
+ raise AssertionError(
157
+ f"ensures {cond!r} raised {type(e).__name__}: {e}"
158
+ ) from e
159
+ if not holds:
160
+ failure.update(kwargs=kwargs, result=result, cond=cond, error=None)
161
+ raise AssertionError(f"ensures {cond!r} is False")
162
+
163
+ try:
164
+ run()
165
+ except AssertionError:
166
+ kwargs = failure.get("kwargs", {})
167
+ call = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
168
+ cond = failure.get("cond", "?")
169
+ result = failure.get("result", "?")
170
+ err = failure.get("error")
171
+ if err:
172
+ line = (
173
+ f"function raised {type(err).__name__}: {err}"
174
+ if cond == "(function call)"
175
+ else f"ensures {cond!r} raised {type(err).__name__}: {err}"
176
+ )
177
+ else:
178
+ line = f"ensures {cond!r} is False"
179
+ detail = (
180
+ f"{name}({call})\n"
181
+ f" returned: {result!r}\n"
182
+ f" {line}"
183
+ )
184
+ return CheckResult(
185
+ "properties", name, Status.FAIL,
186
+ summary=f"falsified: {line}", detail=detail,
187
+ )
188
+ except Exception as e: # noqa: BLE001
189
+ return CheckResult(
190
+ "properties", name, Status.ERROR,
191
+ summary=f"property run errored: {e}", detail=str(e),
192
+ )
193
+ return CheckResult(
194
+ "properties", name, Status.PASS,
195
+ summary=f"{len(sp.ensures)} ensures held over {_MAX_EXAMPLES} examples",
196
+ )
@@ -0,0 +1,50 @@
1
+ """Types verifier — run mypy over a file (optional).
2
+
3
+ Runs once per file, not per function. If mypy is not installed the check is
4
+ skipped (not failed), so mypy stays an optional dependency.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib.util
10
+ import subprocess
11
+ import sys
12
+
13
+ from ._result import CheckResult, Status
14
+
15
+ _MYPY_AVAILABLE: bool | None = None
16
+
17
+
18
+ def _mypy_available() -> bool:
19
+ global _MYPY_AVAILABLE
20
+ if _MYPY_AVAILABLE is None:
21
+ _MYPY_AVAILABLE = importlib.util.find_spec("mypy") is not None
22
+ return _MYPY_AVAILABLE
23
+
24
+
25
+ def verify_types(filename: str) -> list[CheckResult]:
26
+ target = filename
27
+ if not _mypy_available():
28
+ return [CheckResult("types", target, Status.SKIPPED,
29
+ summary="mypy not installed (pip install pyintent[types])")]
30
+ try:
31
+ proc = subprocess.run(
32
+ [sys.executable, "-m", "mypy",
33
+ "--no-error-summary", "--hide-error-context",
34
+ "--no-color-output", "--follow-imports=silent",
35
+ "--ignore-missing-imports", filename],
36
+ capture_output=True, text=True, timeout=120,
37
+ )
38
+ except subprocess.TimeoutExpired:
39
+ return [CheckResult("types", target, Status.ERROR, summary="mypy timed out")]
40
+ except Exception as e: # noqa: BLE001
41
+ return [CheckResult("types", target, Status.ERROR, summary=f"mypy failed to run: {e}")]
42
+
43
+ out = (proc.stdout + proc.stderr).strip()
44
+ if proc.returncode == 0:
45
+ return [CheckResult("types", target, Status.PASS, summary="mypy clean")]
46
+ if proc.returncode == 1:
47
+ return [CheckResult("types", target, Status.FAIL,
48
+ summary="mypy reported type errors", detail=out)]
49
+ return [CheckResult("types", target, Status.ERROR,
50
+ summary="mypy usage error", detail=out)]
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyintent
3
+ Version: 0.1.0
4
+ Summary: Verify that implementations satisfy intent specifications. A pure verifier for AI-generated Python code — it checks, it never generates.
5
+ Author: pyintent contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cshaley/pyintent
8
+ Project-URL: Repository, https://github.com/cshaley/pyintent
9
+ Project-URL: Bug Tracker, https://github.com/cshaley/pyintent/issues
10
+ Project-URL: Changelog, https://github.com/cshaley/pyintent/blob/main/CHANGELOG.md
11
+ Keywords: verification,specification,ai,codegen,contracts,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: click>=8.0
25
+ Requires-Dist: hypothesis>=6.0
26
+ Requires-Dist: pytest>=7.0
27
+ Provides-Extra: types
28
+ Requires-Dist: mypy>=1.0; extra == "types"
29
+ Provides-Extra: async
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == "async"
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy>=1.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
34
+ Provides-Extra: evals
35
+ Requires-Dist: openai>=1.40; extra == "evals"
36
+ Dynamic: license-file
37
+
38
+ # pyintent
39
+
40
+ Write your intent as a specification. Let any AI coding tool write the implementation. **pyintent verifies the implementation actually satisfies the intent.**
41
+
42
+ 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.
43
+
44
+ ```python
45
+ from pyintent import spec, reads, throws
46
+
47
+ @spec(
48
+ intent = "Return the order with the given id from the database.",
49
+ where = ["order_id > 0"],
50
+ ensures = ["result.id == order_id"],
51
+ effects = [reads("db"), throws(NotFoundError)],
52
+ ex = [
53
+ "(42,) -> _",
54
+ "(999,) -> raises NotFoundError",
55
+ "(0,) -> raises ValueError",
56
+ ],
57
+ )
58
+ def find_order(order_id: int) -> Order:
59
+ ... # implemented by Claude Code / Copilot / Devin / you
60
+ ```
61
+
62
+ Then:
63
+
64
+ ```bash
65
+ pyintent verify myapp/orders.py # run all verifiers, human-readable report
66
+ pytest --pyintent # specs become pytest items automatically
67
+ ```
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install pyintent # core (examples, properties, effects)
73
+ pip install pyintent[types] # + mypy integration
74
+ pip install pyintent[dev] # + mypy + pytest-asyncio
75
+ ```
76
+
77
+ ## Quick start
78
+
79
+ ```python
80
+ from pyintent import spec, pure
81
+
82
+ @spec(
83
+ intent = "Return the absolute value of x.",
84
+ effects = [pure],
85
+ ensures = ["result >= 0", "result == x or result == -x"],
86
+ ex = ["(3,) -> 3", "(-4,) -> 4", "(0,) -> 0"],
87
+ )
88
+ def my_abs(x: int) -> int:
89
+ return x if x >= 0 else -x
90
+ ```
91
+
92
+ ```bash
93
+ $ pyintent verify mymodule.py
94
+ [PASS] examples my_abs (3,) -> 3
95
+ [PASS] examples my_abs (-4,) -> 4
96
+ [PASS] examples my_abs (0,) -> 0
97
+ [PASS] properties my_abs
98
+ [PASS] effects my_abs pure
99
+ [PASS] types mymodule.py
100
+
101
+ 3 passed 0 failed 0 errored 0 skipped
102
+ ```
103
+
104
+ ## The `@spec` decorator
105
+
106
+ `@spec` accepts these fields:
107
+
108
+ | Field | Type | Description |
109
+ |--------------|---------------------|-------------|
110
+ | `intent` | `str` (required) | One-line description of what the function does and why. |
111
+ | `where` | `list[str]` | Preconditions — Python expressions that must hold over the inputs. |
112
+ | `ensures` | `list[str]` | Postconditions — Python expressions over inputs and `result`. |
113
+ | `effects` | `list[Effect]` | Declared side-effects (see below). |
114
+ | `ex` | `list[str]` | Runnable examples in `"(args) -> expected"` format. |
115
+ | `perf` | `Perf` | Advisory complexity, e.g. `Perf(time="O(n)")`. |
116
+ | `invariants` | `list[str]` | Class/module-level invariants (plain strings or expressions). |
117
+
118
+ `@spec` must be the **outermost** decorator and returns the target **unchanged** — it only attaches metadata, so there is zero runtime overhead.
119
+
120
+ ## Verifiers
121
+
122
+ ### `examples` — run concrete cases
123
+
124
+ Each `ex` string has the format `"(args) -> expected"`:
125
+
126
+ ```python
127
+ ex = [
128
+ "(1, 2) -> 3", # must return 3
129
+ "(0,) -> raises ValueError", # must raise ValueError
130
+ "('hi',) -> _", # wildcard: any return without raising
131
+ ]
132
+ ```
133
+
134
+ - The left side is a tuple literal (single-arg tuples need a trailing comma: `(42,)`).
135
+ - `raises ExcType` matches if the call raises that type or a subclass.
136
+ - `_` matches any non-raising return.
137
+ - Values are evaluated in the module's global namespace, so domain objects and enums resolve correctly.
138
+
139
+ ### `properties` — hypothesis-based postcondition testing
140
+
141
+ For functions with `ensures` and no impure effects, pyintent generates inputs from type hints using [Hypothesis](https://hypothesis.readthedocs.io/), filters them through `where`, and asserts every `ensures` expression:
142
+
143
+ ```python
144
+ @spec(
145
+ intent = "Sort a list of integers in ascending order.",
146
+ effects = [pure],
147
+ where = ["len(xs) < 1000"],
148
+ ensures = [
149
+ "len(result) == len(xs)",
150
+ "all(result[i] <= result[i+1] for i in range(len(result)-1))",
151
+ ],
152
+ )
153
+ def sort_ints(xs: list[int]) -> list[int]:
154
+ return sorted(xs)
155
+ ```
156
+
157
+ `ensures` expressions may reference input parameters and `result` (the return value).
158
+
159
+ ### `types` — mypy integration
160
+
161
+ Runs `mypy` over the target file. Skipped gracefully if mypy is not installed. Install it with `pip install pyintent[types]`.
162
+
163
+ ### `effects` — AST-based effect checking
164
+
165
+ Three effects are actively verified in v0.1:
166
+
167
+ | Effect | What is checked |
168
+ |--------|----------------|
169
+ | `pure` | No calls to impure builtins (`print`, `open`, …) or modules (`os`, `sys`, `random`, `requests`, …), no `global`/`nonlocal` writes. |
170
+ | `async_` | The function must be defined with `async def`. |
171
+ | `throws(ExcA, ExcB)` | Every explicitly raised exception type is declared. |
172
+
173
+ These effects are **declaration-only** (recorded but not verified):
174
+ `reads("db")`, `writes("cache")`, `network("stripe")`, `io`
175
+
176
+ A function may combine multiple effects:
177
+ ```python
178
+ effects = [reads("db"), throws(NotFoundError, ValueError)]
179
+ ```
180
+
181
+ ## CLI usage
182
+
183
+ ```bash
184
+ # Write the spec-authoring guide into your AI tool's prompt files
185
+ # (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, etc.)
186
+ pyintent init
187
+
188
+ # Print the spec-authoring guide to stdout
189
+ pyintent prompt
190
+
191
+ # Validate spec structure by importing files (no execution)
192
+ pyintent check myapp/
193
+
194
+ # Require every public function to have a @spec
195
+ pyintent check --require-specs myapp/
196
+
197
+ # Run all verifiers and report results
198
+ pyintent verify myapp/orders.py
199
+ pyintent verify myapp/
200
+
201
+ # Machine-readable JSON output
202
+ pyintent verify --json myapp/ > results.json
203
+
204
+ # Run only specific verifiers
205
+ pyintent verify --only examples --only properties myapp/
206
+ ```
207
+
208
+ Exit codes: `0` all good, `1` verification failures, `2` usage or load error.
209
+
210
+ ## pytest plugin
211
+
212
+ The pytest plugin is opt-in — installing pyintent does not change how existing `pytest` runs behave.
213
+
214
+ Enable it on the command line:
215
+
216
+ ```bash
217
+ pytest --pyintent
218
+ ```
219
+
220
+ Or permanently in `pyproject.toml`:
221
+
222
+ ```toml
223
+ [tool.pytest.ini_options]
224
+ pyintent = true
225
+ ```
226
+
227
+ Each spec becomes one or more pytest items:
228
+
229
+ - One item per `ex` case
230
+ - One item for property testing (if `ensures` is set)
231
+ - One item for the type check per file
232
+
233
+ ## pyproject.toml configuration
234
+
235
+ ```toml
236
+ [tool.pyintent]
237
+ require_specs = true # or "all" to also require class/module specs
238
+ exclude = ["migrations", "tests"]
239
+ ```
240
+
241
+ ## Safety
242
+
243
+ 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.
244
+
245
+ ## Status
246
+
247
+ 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.
248
+
249
+ ## Contributing
250
+
251
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
252
+
253
+ ## License
254
+
255
+ [MIT](LICENSE)
@@ -0,0 +1,25 @@
1
+ pyintent/__init__.py,sha256=b3S-xTB6Oz7WApkBjGmbS4av6rpbjODGozp3vue6xMU,989
2
+ pyintent/_discovery.py,sha256=AmbCN1MLkMlU75sWjiPq1x1-Ku0L5lBAeaG0PRqcESg,3382
3
+ pyintent/_effects.py,sha256=n8NM63eEjyPxm50pINFR7Mb-phXu9Oe37NJ4pfF6lSE,5185
4
+ pyintent/_errors.py,sha256=vJ0U94AoY-M9cc6kuNXJIrsxVUI42aDQaeZUDYTpeRc,390
5
+ pyintent/_loader.py,sha256=dgfjuGg8VTV1h4pYdoLw3hB7OKYgpYRUldXseObPptU,1963
6
+ pyintent/_module_spec.py,sha256=_RGBWyR9h48x2ll7UpUMoSPPTAglWKbKU-mqOc6LCDI,1953
7
+ pyintent/_parser.py,sha256=W7qQrfs28HYyIBcrr2FqCVCWCMwtyNl9LjMIZKoKlTg,6734
8
+ pyintent/_perf.py,sha256=iRqeUQ2mHA29otofwiE2jb-lYSdW974LLB0SCoQ51oA,1714
9
+ pyintent/_spec.py,sha256=wj_q2Fdr_qGXXBhOBSBK4Rjn0nr-tuTCuiKE7eOT-og,12740
10
+ pyintent/cli.py,sha256=wwNv6qZO0ixIOUz3GWEOhueLS8E5Tkvc4ZZZoboQMA0,9048
11
+ pyintent/plugin.py,sha256=UulqBG4EprdniZTs4TMvJRsZIuj7UfOCdSTH_PwYu_s,5062
12
+ pyintent/prompt.py,sha256=N19AJW85PeKR6zM_cxf3Qyem_vIVBeVAdEUUIl4C_e4,5639
13
+ pyintent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ pyintent/verifier/__init__.py,sha256=PbJq44_hq72adTpfxr8Nk9-AGwsJyz15IBwdW_pCBwA,1861
15
+ pyintent/verifier/_result.py,sha256=oicuvu82gNYy6Wmfg3iYtlg1YQTRZRWckaRR6O4PQtU,1177
16
+ pyintent/verifier/effects.py,sha256=vdss3N-FvXgEWfbK_vuOTFZsb9GCqHQIJWPHkTfFGzk,6116
17
+ pyintent/verifier/examples.py,sha256=gplxv4XUe5j_CAAacHKQgeyBPydd2YKUcB3ZmTsUH9w,4836
18
+ pyintent/verifier/properties.py,sha256=yjOc6drKyZZ8QZRF5MSnQ4NdDeo7TvdP5dugBMkT97w,6848
19
+ pyintent/verifier/types.py,sha256=K_fm_8cWQVLkUn1dHgoxx-2EYJA4sQ9wjES3GBzdIK8,1838
20
+ pyintent-0.1.0.dist-info/licenses/LICENSE,sha256=ZzxQUeFTwIbKJdbV9IvI7MkFcvQnYTnQfWV6fjeQnJA,1078
21
+ pyintent-0.1.0.dist-info/METADATA,sha256=62fSIjCyxJhGBAxRlyfdrfLPbgCJf5w-aarRQjitTLE,8599
22
+ pyintent-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
23
+ pyintent-0.1.0.dist-info/entry_points.txt,sha256=d6yq9D480QpMIOMeQGsgooieLSj7zYW2vbclE6c44So,86
24
+ pyintent-0.1.0.dist-info/top_level.txt,sha256=RTfkCed7A1J1TuH7z-LrL-8gsDoJqLhC9opv2cKZfuI,9
25
+ pyintent-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ pyintent = pyintent.cli:main
3
+
4
+ [pytest11]
5
+ pyintent = pyintent.plugin