invar-tools 1.2.0__py3-none-any.whl → 1.3.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.
- invar/__init__.py +1 -0
- invar/core/contracts.py +10 -10
- invar/core/entry_points.py +105 -32
- invar/core/extraction.py +5 -6
- invar/core/format_specs.py +1 -2
- invar/core/formatter.py +6 -7
- invar/core/hypothesis_strategies.py +5 -7
- invar/core/inspect.py +1 -1
- invar/core/lambda_helpers.py +3 -3
- invar/core/models.py +7 -1
- invar/core/must_use.py +2 -1
- invar/core/parser.py +7 -4
- invar/core/postcondition_scope.py +128 -0
- invar/core/property_gen.py +8 -5
- invar/core/purity.py +3 -3
- invar/core/purity_heuristics.py +5 -9
- invar/core/references.py +8 -6
- invar/core/review_trigger.py +78 -6
- invar/core/rule_meta.py +8 -0
- invar/core/rules.py +18 -19
- invar/core/shell_analysis.py +5 -10
- invar/core/shell_architecture.py +2 -2
- invar/core/strategies.py +7 -14
- invar/core/suggestions.py +86 -0
- invar/core/sync_helpers.py +238 -0
- invar/core/tautology.py +102 -37
- invar/core/template_parser.py +467 -0
- invar/core/timeout_inference.py +4 -7
- invar/core/utils.py +13 -15
- invar/core/verification_routing.py +4 -7
- invar/mcp/server.py +100 -17
- invar/shell/commands/__init__.py +11 -0
- invar/shell/{cli.py → commands/guard.py} +94 -14
- invar/shell/{init_cmd.py → commands/init.py} +179 -27
- invar/shell/commands/merge.py +256 -0
- invar/shell/commands/sync_self.py +113 -0
- invar/shell/commands/template_sync.py +366 -0
- invar/shell/commands/update.py +48 -0
- invar/shell/config.py +12 -24
- invar/shell/coverage.py +351 -0
- invar/shell/guard_helpers.py +38 -17
- invar/shell/guard_output.py +7 -1
- invar/shell/property_tests.py +58 -22
- invar/shell/prove/__init__.py +9 -0
- invar/shell/{prove.py → prove/crosshair.py} +40 -33
- invar/shell/{prove_fallback.py → prove/hypothesis.py} +12 -4
- invar/shell/subprocess_env.py +393 -0
- invar/shell/template_engine.py +345 -0
- invar/shell/templates.py +19 -0
- invar/shell/testing.py +71 -20
- invar/templates/CLAUDE.md.template +38 -17
- invar/templates/aider.conf.yml.template +2 -2
- invar/templates/commands/{review.md → audit.md} +20 -82
- invar/templates/commands/guard.md +77 -0
- invar/templates/config/CLAUDE.md.jinja +206 -0
- invar/templates/config/context.md.jinja +92 -0
- invar/templates/config/pre-commit.yaml.jinja +44 -0
- invar/templates/context.md.template +33 -0
- invar/templates/cursorrules.template +7 -4
- invar/templates/examples/README.md +2 -0
- invar/templates/examples/conftest.py +3 -0
- invar/templates/examples/contracts.py +5 -5
- invar/templates/examples/core_shell.py +11 -7
- invar/templates/examples/workflow.md +81 -0
- invar/templates/manifest.toml +137 -0
- invar/templates/{INVAR.md → protocol/INVAR.md} +10 -7
- invar/templates/skills/develop/SKILL.md.jinja +318 -0
- invar/templates/skills/investigate/SKILL.md.jinja +106 -0
- invar/templates/skills/propose/SKILL.md.jinja +104 -0
- invar/templates/skills/review/SKILL.md.jinja +125 -0
- {invar_tools-1.2.0.dist-info → invar_tools-1.3.0.dist-info}/METADATA +108 -118
- invar_tools-1.3.0.dist-info/RECORD +95 -0
- invar_tools-1.3.0.dist-info/entry_points.txt +2 -0
- invar/contracts.py +0 -152
- invar/decorators.py +0 -94
- invar/invariant.py +0 -58
- invar/resource.py +0 -99
- invar/shell/update_cmd.py +0 -193
- invar_tools-1.2.0.dist-info/RECORD +0 -77
- invar_tools-1.2.0.dist-info/entry_points.txt +0 -2
- /invar/shell/{mutate_cmd.py → commands/mutate.py} +0 -0
- /invar/shell/{perception.py → commands/perception.py} +0 -0
- /invar/shell/{test_cmd.py → commands/test.py} +0 -0
- /invar/shell/{prove_accept.py → prove/accept.py} +0 -0
- /invar/shell/{prove_cache.py → prove/cache.py} +0 -0
- {invar_tools-1.2.0.dist-info → invar_tools-1.3.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.2.0.dist-info → invar_tools-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.2.0.dist-info → invar_tools-1.3.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.2.0.dist-info → invar_tools-1.3.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: invar-tools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: AI-native software engineering tools with design-by-contract verification
|
|
5
5
|
Project-URL: Homepage, https://github.com/tefx/invar
|
|
6
6
|
Project-URL: Documentation, https://github.com/tefx/invar#readme
|
|
@@ -24,6 +24,7 @@ Requires-Python: >=3.11
|
|
|
24
24
|
Requires-Dist: crosshair-tool>=0.0.60
|
|
25
25
|
Requires-Dist: hypothesis>=6.0
|
|
26
26
|
Requires-Dist: invar-runtime>=1.0
|
|
27
|
+
Requires-Dist: jinja2>=3.0
|
|
27
28
|
Requires-Dist: mcp>=1.0
|
|
28
29
|
Requires-Dist: pre-commit>=3.0
|
|
29
30
|
Requires-Dist: pydantic>=2.0
|
|
@@ -31,6 +32,7 @@ Requires-Dist: returns>=0.20
|
|
|
31
32
|
Requires-Dist: rich>=13.0
|
|
32
33
|
Requires-Dist: typer>=0.9
|
|
33
34
|
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: coverage[toml]>=7.0; extra == 'dev'
|
|
34
36
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
35
37
|
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
36
38
|
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
@@ -43,9 +45,9 @@ Description-Content-Type: text/markdown
|
|
|
43
45
|
[](https://www.python.org/downloads/)
|
|
44
46
|
[](#license)
|
|
45
47
|
|
|
46
|
-
**Don't hope AI code is correct. Know it.**
|
|
48
|
+
> **Don't hope AI code is correct. Know it.**
|
|
47
49
|
|
|
48
|
-
Invar is a framework
|
|
50
|
+
Invar is a verification framework for AI-assisted development. It provides contracts, verification, and a structured workflow methodology.
|
|
49
51
|
|
|
50
52
|
```python
|
|
51
53
|
from invar_runtime import pre, post
|
|
@@ -62,6 +64,18 @@ def average(items: list[float]) -> float:
|
|
|
62
64
|
|
|
63
65
|
---
|
|
64
66
|
|
|
67
|
+
## Experience Tiers
|
|
68
|
+
|
|
69
|
+
| Platform | Experience | Features |
|
|
70
|
+
|----------|------------|----------|
|
|
71
|
+
| **Claude Code** | Full | USBV workflow + skill automation + MCP integration |
|
|
72
|
+
| **Cursor/Windsurf** | Basic | INVAR.md protocol + CLI verification |
|
|
73
|
+
| **Other editors** | Minimal | CLI verification tools only |
|
|
74
|
+
|
|
75
|
+
**Why tiers?** The skill system (`/develop`, `/review`, etc.) requires Claude Code's sub-agent capabilities. Other editors receive the protocol document and CLI tools.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
65
79
|
## Installation
|
|
66
80
|
|
|
67
81
|
### Two Packages, Different Purposes
|
|
@@ -77,69 +91,85 @@ def average(items: list[float]) -> float:
|
|
|
77
91
|
└─────────────────────────────────────────────────────────────────┘
|
|
78
92
|
```
|
|
79
93
|
|
|
80
|
-
| Package | Install |
|
|
81
|
-
|
|
94
|
+
| Package | Install | Purpose |
|
|
95
|
+
|---------|---------|---------|
|
|
82
96
|
| **invar-tools** | `uvx invar-tools <cmd>` | Development: verification, init, MCP server |
|
|
83
97
|
| **invar-runtime** | `pip install invar-runtime` | Production: add to your project's dependencies |
|
|
84
98
|
|
|
85
|
-
###
|
|
99
|
+
### Quick Install
|
|
86
100
|
|
|
87
101
|
```bash
|
|
88
|
-
#
|
|
102
|
+
# Development tools (recommended: use without installing)
|
|
89
103
|
uvx invar-tools guard
|
|
90
104
|
uvx invar-tools init --claude
|
|
91
|
-
uvx invar-tools map --top 10
|
|
92
|
-
|
|
93
|
-
# Or install globally
|
|
94
|
-
pip install invar-tools
|
|
95
|
-
invar guard
|
|
96
|
-
```
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Add to your project's `requirements.txt` or `pyproject.toml`:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
106
|
+
# Runtime contracts (add to your project)
|
|
103
107
|
pip install invar-runtime
|
|
104
108
|
```
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
**Why uvx is recommended:**
|
|
111
|
+
- Always uses latest version
|
|
112
|
+
- Doesn't pollute project dependencies
|
|
113
|
+
- Automatically accesses your project's venv dependencies (numpy, pandas, etc.)
|
|
114
|
+
- If your project has `invar-tools` installed, uvx will detect and use it
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
...
|
|
114
|
-
```
|
|
116
|
+
**When to use pip install instead:**
|
|
117
|
+
- CI/CD environments where uvx isn't available
|
|
118
|
+
- Projects with C extensions AND Python version mismatch between uvx and project
|
|
115
119
|
|
|
116
120
|
---
|
|
117
121
|
|
|
118
122
|
## Quick Start
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
|
-
# 1. Initialize your project
|
|
125
|
+
# 1. Initialize your project
|
|
122
126
|
cd your-project
|
|
123
|
-
uvx invar-tools init --claude #
|
|
127
|
+
uvx invar-tools init --claude # Full experience (Claude Code)
|
|
128
|
+
uvx invar-tools init # Basic experience (other editors)
|
|
124
129
|
|
|
125
130
|
# 2. Write code with AI (AI follows INVAR.md protocol)
|
|
126
131
|
|
|
127
132
|
# 3. Verify code quality
|
|
128
|
-
uvx invar-tools guard # Runs static analysis + doctests
|
|
133
|
+
uvx invar-tools guard # Runs static analysis + doctests + property tests
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## The USBV Workflow
|
|
139
|
+
|
|
140
|
+
AI agents follow a four-phase development cycle:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
|
|
144
|
+
│ Understand│ → │ Specify │ → │ Build │ → │ Validate │
|
|
145
|
+
│ │ │ │ │ │ │ │
|
|
146
|
+
│ Intent │ │ @pre/@post│ │ Implement │ │ invar │
|
|
147
|
+
│ Inspect │ │ Doctests │ │ leaves │ │ guard │
|
|
148
|
+
│ Constraints │ Design │ │ first │ │ │
|
|
149
|
+
└───────────┘ └───────────┘ └───────────┘ └───────────┘
|
|
129
150
|
```
|
|
130
151
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
**Key insight:** Specify contracts BEFORE implementation. The contract becomes the specification.
|
|
153
|
+
|
|
154
|
+
See [INVAR.md](./INVAR.md) for complete protocol.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Session Protocol
|
|
134
159
|
|
|
135
|
-
|
|
160
|
+
Every AI session follows this format:
|
|
136
161
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
**First message (Check-In):**
|
|
163
|
+
```
|
|
164
|
+
✓ Check-In: [project] | [branch] | [clean/dirty]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Last message (Final):**
|
|
168
|
+
```
|
|
169
|
+
✓ Final: guard PASS | 0 errors, 2 warnings
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Check-In shows project context. Guard verification runs during VALIDATE phase and Final, not at Check-In.
|
|
143
173
|
|
|
144
174
|
---
|
|
145
175
|
|
|
@@ -149,8 +179,8 @@ Invar enforces separation between pure logic and I/O:
|
|
|
149
179
|
|
|
150
180
|
| Zone | Requirements | Forbidden |
|
|
151
181
|
|------|--------------|-----------|
|
|
152
|
-
| **Core** | `@pre`/`@post` contracts, doctests | I/O imports (os, pathlib, requests...) |
|
|
153
|
-
| **Shell** | `Result[T, E]` returns | - |
|
|
182
|
+
| **Core** (`**/core/**`) | `@pre`/`@post` contracts, doctests | I/O imports (os, pathlib, requests...) |
|
|
183
|
+
| **Shell** (`**/shell/**`) | `Result[T, E]` returns | - |
|
|
154
184
|
|
|
155
185
|
```python
|
|
156
186
|
# Core: Pure logic, receives data
|
|
@@ -170,9 +200,10 @@ def load_config(path: Path) -> Result[Config, str]:
|
|
|
170
200
|
### Guard (Primary)
|
|
171
201
|
|
|
172
202
|
```bash
|
|
173
|
-
invar guard # Full verification (
|
|
203
|
+
invar guard # Full verification (static + doctests + property tests)
|
|
174
204
|
invar guard --changed # Only git-modified files
|
|
175
|
-
invar guard --static # Static only (~0.5s)
|
|
205
|
+
invar guard --static # Static analysis only (~0.5s)
|
|
206
|
+
invar guard --coverage # Collect branch coverage (doctest + hypothesis)
|
|
176
207
|
```
|
|
177
208
|
|
|
178
209
|
**Flags:**
|
|
@@ -180,9 +211,9 @@ invar guard --static # Static only (~0.5s)
|
|
|
180
211
|
| Flag | Purpose |
|
|
181
212
|
|------|---------|
|
|
182
213
|
| `--strict` | Treat warnings as errors |
|
|
183
|
-
| `--explain` | Show rule
|
|
214
|
+
| `--explain` | Show rule explanations |
|
|
184
215
|
| `--agent` | JSON output for AI tools |
|
|
185
|
-
| `--
|
|
216
|
+
| `--coverage` | Branch coverage from doctest + hypothesis phases |
|
|
186
217
|
|
|
187
218
|
### Other Commands
|
|
188
219
|
|
|
@@ -195,14 +226,18 @@ invar update # Update managed files
|
|
|
195
226
|
|
|
196
227
|
---
|
|
197
228
|
|
|
198
|
-
##
|
|
229
|
+
## Workflow Skills (Claude Code)
|
|
230
|
+
|
|
231
|
+
`invar init --claude` creates workflow skills in `.claude/skills/`:
|
|
199
232
|
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
|
|
|
203
|
-
|
|
|
233
|
+
| Skill | Trigger | Purpose |
|
|
234
|
+
|-------|---------|---------|
|
|
235
|
+
| `/investigate` | "why", "explain", vague tasks | Exploration, no code changes |
|
|
236
|
+
| `/propose` | "should we", "compare" | Decision facilitation with options |
|
|
237
|
+
| `/develop` | "add", "fix", "implement" | USBV implementation workflow |
|
|
238
|
+
| `/review` | After develop, `review_suggested` | Adversarial review with fix loop |
|
|
204
239
|
|
|
205
|
-
**
|
|
240
|
+
**Note:** Skills are Claude Code exclusive. Other editors use INVAR.md protocol directly.
|
|
206
241
|
|
|
207
242
|
---
|
|
208
243
|
|
|
@@ -231,18 +266,10 @@ require_doctests = true
|
|
|
231
266
|
# Doctest-heavy code
|
|
232
267
|
exclude_doctest_lines = true
|
|
233
268
|
|
|
234
|
-
# Purity overrides
|
|
235
|
-
purity_pure = ["pandas.DataFrame.groupby"]
|
|
236
|
-
purity_impure = ["my_module.cached_lookup"]
|
|
237
|
-
|
|
238
269
|
# Rule exclusions
|
|
239
270
|
[[tool.invar.guard.rule_exclusions]]
|
|
240
271
|
pattern = "**/generated/**"
|
|
241
272
|
rules = ["*"]
|
|
242
|
-
|
|
243
|
-
# Severity overrides
|
|
244
|
-
[tool.invar.guard.severity_overrides]
|
|
245
|
-
redundant_type_contract = "off"
|
|
246
273
|
```
|
|
247
274
|
|
|
248
275
|
---
|
|
@@ -258,7 +285,6 @@ redundant_type_contract = "off"
|
|
|
258
285
|
| `forbidden_import` | ERROR | I/O import in Core |
|
|
259
286
|
| `shell_result` | WARN | Shell function not returning Result |
|
|
260
287
|
| `empty_contract` | ERROR | Contract is `lambda: True` |
|
|
261
|
-
| `param_mismatch` | ERROR | Lambda params ≠ function params |
|
|
262
288
|
|
|
263
289
|
Full list: `invar rules --explain`
|
|
264
290
|
|
|
@@ -266,7 +292,7 @@ Full list: `invar rules --explain`
|
|
|
266
292
|
|
|
267
293
|
## MCP Integration (Claude Code)
|
|
268
294
|
|
|
269
|
-
`invar init` automatically
|
|
295
|
+
`invar init --claude` automatically configures MCP:
|
|
270
296
|
|
|
271
297
|
```json
|
|
272
298
|
{
|
|
@@ -279,46 +305,29 @@ Full list: `invar rules --explain`
|
|
|
279
305
|
}
|
|
280
306
|
```
|
|
281
307
|
|
|
282
|
-
**Claude Code Integration:**
|
|
283
|
-
|
|
284
|
-
```bash
|
|
285
|
-
# Full integration (recommended)
|
|
286
|
-
invar init --claude
|
|
287
|
-
|
|
288
|
-
# Specify MCP method
|
|
289
|
-
invar init --claude --mcp-method uvx # Recommended
|
|
290
|
-
invar init --claude --mcp-method command # Use PATH invar
|
|
291
|
-
invar init --claude --mcp-method python # Use current Python
|
|
292
|
-
```
|
|
293
|
-
|
|
294
308
|
**MCP Tools:**
|
|
295
309
|
|
|
296
310
|
| Tool | Replaces | Purpose |
|
|
297
311
|
|------|----------|---------|
|
|
298
|
-
| `invar_guard` | `pytest`, `crosshair` | Smart
|
|
299
|
-
| `invar_sig` | Reading entire
|
|
312
|
+
| `invar_guard` | `pytest`, `crosshair` | Smart verification |
|
|
313
|
+
| `invar_sig` | Reading entire files | Show contracts and signatures |
|
|
300
314
|
| `invar_map` | `grep` for functions | Symbol map with reference counts |
|
|
301
315
|
|
|
302
|
-
Manual setup: See `.invar/mcp-setup.md` after running `invar init`.
|
|
303
|
-
|
|
304
316
|
---
|
|
305
317
|
|
|
306
|
-
## Platform Support
|
|
307
|
-
|
|
308
|
-
The Independent Adversarial Reviewer (DX-31) has different capabilities depending on your AI coding assistant:
|
|
318
|
+
## Platform Support
|
|
309
319
|
|
|
310
|
-
| Feature | Claude Code |
|
|
311
|
-
|
|
312
|
-
| Guard
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
320
|
+
| Feature | Claude Code | Cursor/Windsurf | Others |
|
|
321
|
+
|---------|-------------|-----------------|--------|
|
|
322
|
+
| Smart Guard CLI | ✅ | ✅ | ✅ |
|
|
323
|
+
| INVAR.md protocol | ✅ | ✅ | ✅ |
|
|
324
|
+
| MCP integration | ✅ | ❌ | ❌ |
|
|
325
|
+
| Workflow skills | ✅ | ❌ | ❌ |
|
|
326
|
+
| Sub-agent review | ✅ | ❌ | ❌ |
|
|
316
327
|
|
|
317
|
-
**Claude Code
|
|
328
|
+
**Claude Code** provides the full experience with automated workflow and independent review.
|
|
318
329
|
|
|
319
|
-
**
|
|
320
|
-
|
|
321
|
-
**Why context isolation matters:** The same agent that wrote code has "author blindness" — it remembers its rationale and unconsciously validates its own decisions. An isolated reviewer sees only the code.
|
|
330
|
+
**Other editors** receive the protocol and CLI tools. Workflow adherence is manual.
|
|
322
331
|
|
|
323
332
|
---
|
|
324
333
|
|
|
@@ -328,7 +337,7 @@ The Independent Adversarial Reviewer (DX-31) has different capabilities dependin
|
|
|
328
337
|
|------|-------|-------|
|
|
329
338
|
| `INVAR.md` | Invar | No (`invar update` manages) |
|
|
330
339
|
| `CLAUDE.md` | You | Yes (project config) |
|
|
331
|
-
| `.
|
|
340
|
+
| `.claude/skills/` | You | Yes (customize workflows) |
|
|
332
341
|
|
|
333
342
|
---
|
|
334
343
|
|
|
@@ -337,51 +346,32 @@ The Independent Adversarial Reviewer (DX-31) has different capabilities dependin
|
|
|
337
346
|
Contracts are checked at runtime via [deal](https://github.com/life4/deal).
|
|
338
347
|
|
|
339
348
|
```bash
|
|
340
|
-
# Disable in production
|
|
349
|
+
# Disable in production for performance
|
|
341
350
|
DEAL_DISABLE=1 python app.py
|
|
342
351
|
```
|
|
343
352
|
|
|
344
353
|
---
|
|
345
354
|
|
|
346
|
-
## Troubleshooting
|
|
347
|
-
|
|
348
|
-
**Guard is slow:**
|
|
349
|
-
- Use `--changed` for incremental checks
|
|
350
|
-
- Use `--static` to skip doctests during debugging
|
|
351
|
-
|
|
352
|
-
**Too many warnings:**
|
|
353
|
-
- Add exclusions for generated code
|
|
354
|
-
- Override severity for noisy rules
|
|
355
|
-
|
|
356
|
-
**CrossHair timeout:**
|
|
357
|
-
- Some code patterns don't work well with symbolic execution
|
|
358
|
-
- Hypothesis fallback runs automatically
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
355
|
## Learn More
|
|
363
356
|
|
|
364
357
|
**In your project** (created by `invar init`):
|
|
365
|
-
- `INVAR.md` — Protocol for AI agents
|
|
358
|
+
- `INVAR.md` — Protocol v5.0 for AI agents
|
|
366
359
|
- `CLAUDE.md` — Project configuration
|
|
367
360
|
- `.invar/examples/` — Reference patterns
|
|
368
361
|
|
|
369
362
|
**Documentation:**
|
|
370
|
-
- [docs/
|
|
371
|
-
- [docs/
|
|
363
|
+
- [docs/vision.md](./docs/vision.md) — Design philosophy
|
|
364
|
+
- [docs/design.md](./docs/design.md) — Technical architecture
|
|
365
|
+
- [GitHub Pages](https://tefx.github.io/Invar/) — Visual overview
|
|
372
366
|
|
|
373
367
|
---
|
|
374
368
|
|
|
375
369
|
## License
|
|
376
370
|
|
|
377
|
-
This project uses a dual-license structure:
|
|
378
|
-
|
|
379
371
|
| Component | License | Purpose |
|
|
380
372
|
|-----------|---------|---------|
|
|
381
|
-
| **invar-runtime** | [Apache-2.0](LICENSE) | Runtime contracts - use freely
|
|
382
|
-
| **invar-tools** | [GPL-3.0](LICENSE-GPL) | CLI tools -
|
|
383
|
-
| **Documentation** | [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) |
|
|
384
|
-
|
|
385
|
-
**For users:** You can use `invar-runtime` in any project (including proprietary). The `invar-tools` CLI is for development only and doesn't affect your project's license.
|
|
373
|
+
| **invar-runtime** | [Apache-2.0](LICENSE) | Runtime contracts - use freely |
|
|
374
|
+
| **invar-tools** | [GPL-3.0](LICENSE-GPL) | CLI tools - improvements shared |
|
|
375
|
+
| **Documentation** | [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) | Share with attribution |
|
|
386
376
|
|
|
387
377
|
See [NOTICE](NOTICE) for third-party licenses.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
invar/__init__.py,sha256=Bzp8MpdH-uUNe6qY9ITv6LLOn0HUomtIToQ0zUS1mkg,1342
|
|
2
|
+
invar/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
invar/core/__init__.py,sha256=01TgQ2bqTFV4VFdksfqXYPa2WUqo-DpUWUkEcIUXFb4,218
|
|
4
|
+
invar/core/contracts.py,sha256=SOyF1KeJ6hrEwfQ09UzMt881OJKDXRbPTslKA6HzdKg,19085
|
|
5
|
+
invar/core/entry_points.py,sha256=gB6h2O9gp9larxDWVA2IUclLadMF_H8eJErnscYm0GU,12012
|
|
6
|
+
invar/core/extraction.py,sha256=mScqEMEEQdsd-Z0jx9g3scK6Z1vI9l-ESjggXPIWHZ4,6112
|
|
7
|
+
invar/core/format_specs.py,sha256=P299aRHFMXyow8STwsvaT6Bg2ALPs2wSy7SByiRZZ-A,5610
|
|
8
|
+
invar/core/format_strategies.py,sha256=LifL97JbsF8WEkVNmQpq2htyFUC3pW21myAjtRGpSxU,5774
|
|
9
|
+
invar/core/formatter.py,sha256=Ct-8x4wYoMNfi6-ub5IjoQJ80mMAXlAhsXweRrxKkGw,10548
|
|
10
|
+
invar/core/hypothesis_strategies.py,sha256=_MfjG7KxkmJvuPsczr_1JayR_YmiDzU2jJ8fQPoKGgs,16517
|
|
11
|
+
invar/core/inspect.py,sha256=l1knohwpLRHSNySPUjyeBHJusnU0vYiQGj4dMVgQZIo,4381
|
|
12
|
+
invar/core/lambda_helpers.py,sha256=Ap1y7N0wpgCgPHwrs2pd7zD9Qq4Ptfd2iTliprXIkME,6457
|
|
13
|
+
invar/core/models.py,sha256=BbGiUQh6QdhHaw1fUQjFT6fm9bOUiHRam3d95rKOKYE,10089
|
|
14
|
+
invar/core/must_use.py,sha256=7HnnbT53lb4dOT-1mL64pz0JbQYytuw4eejNVe7iWKY,5496
|
|
15
|
+
invar/core/parser.py,sha256=ucVpGziVzUvbkXT1n_SgOrYdStDEcNBqLuRGqK3_M5g,9205
|
|
16
|
+
invar/core/postcondition_scope.py,sha256=ykjVNqZZ1zItBmI7ebgmLW5vFGE-vpaLRTvSgWaJMgM,5245
|
|
17
|
+
invar/core/property_gen.py,sha256=_IvBJNUqd8pwu7wXuIProZn5RZksqFdmtXt3GnLU6Vo,14033
|
|
18
|
+
invar/core/purity.py,sha256=dt5dFy5V8Ch93iBJF5OuKUr1jjfimfY3oHLQD8KmLHw,12036
|
|
19
|
+
invar/core/purity_heuristics.py,sha256=vsgphC1XPIFtsoLB0xvp--AyaJHqlh83LyKXYda4pWc,4546
|
|
20
|
+
invar/core/references.py,sha256=64yGIdj9vL72Y4uUhJsi9pztZkuMnLN-7OcOziyxYMo,6339
|
|
21
|
+
invar/core/review_trigger.py,sha256=4GGHUmgbVsQJAob4OO6A8G7KrLcNMwNOuqHiT6Jc7cs,14085
|
|
22
|
+
invar/core/rule_meta.py,sha256=il_KUTjSlW1MOVgLguuLDS9wEdyqUe3CDvUx4gQjACo,10180
|
|
23
|
+
invar/core/rules.py,sha256=6MvqO4dQXYwYe3ICp1bT2s_33O7FkpGwUzOrgHL16X4,19962
|
|
24
|
+
invar/core/shell_analysis.py,sha256=i2A9SMqBI3Rb4Ai0QNTM7awIkSJIY6yZJVWS72lv0bY,6457
|
|
25
|
+
invar/core/shell_architecture.py,sha256=98EVdBFIs8tO-i9jKuzdmv7fLB4PKnyI-vKh5lxnB98,6538
|
|
26
|
+
invar/core/strategies.py,sha256=2DPl0z2p_CBNd4RlSbZzTeAy6Dq6cpCiBCB2p5qHHkk,8798
|
|
27
|
+
invar/core/suggestions.py,sha256=LCg2Dy9EHh_n1t9jATRZ0gTkgJkAEZk3vp2nuuCyr-s,15129
|
|
28
|
+
invar/core/sync_helpers.py,sha256=kd6VyFAcpKfkVcbDk3GaBi2n0EWOGICz4VmdxwbshfI,7523
|
|
29
|
+
invar/core/tautology.py,sha256=Pmn__a0Bt55W0lAQo1G5q8Ory9KuE23dRknKw45xxbs,9221
|
|
30
|
+
invar/core/template_parser.py,sha256=vH3H8OX55scZ1hWh3xoA8oJMhgleKufCOhkTvsSuu_4,14730
|
|
31
|
+
invar/core/timeout_inference.py,sha256=BS2fJGmwOrLpYZUku4qrizgNDSIXVLFBslW-6sRAvpc,3451
|
|
32
|
+
invar/core/utils.py,sha256=4ani-6XcWF__sD0c_tKcCA2FunaF2pYIfvR5BACWkDg,14168
|
|
33
|
+
invar/core/verification_routing.py,sha256=_jXi1txFCcUdnB3-Yavtuyk8N-XhEO_Vu_051Vuz27Y,5020
|
|
34
|
+
invar/mcp/__init__.py,sha256=n3S7QwMjSMqOMT8cI2jf9E0yZPjKmBOJyIYhq4WZ8TQ,226
|
|
35
|
+
invar/mcp/__main__.py,sha256=ZcIT2U6xUyGOWucl4jq422BDE3lRLjqyxb9pFylRBdk,219
|
|
36
|
+
invar/mcp/server.py,sha256=0a9eAuIZ62UaDGewKhAn8-PhLYvFHta-w_XcygcXCtE,11981
|
|
37
|
+
invar/shell/__init__.py,sha256=FFw1mNbh_97PeKPcHIqQpQ7mw-JoIvyLM1yOdxLw5uk,204
|
|
38
|
+
invar/shell/config.py,sha256=cixlq47h8HYa9Ku-JOo66KCUyrf59R0NX_Yb7A1fAv4,16134
|
|
39
|
+
invar/shell/coverage.py,sha256=m01o898IFIdBztEBQLwwL1Vt5PWrpUntO4lv4nWEkls,11344
|
|
40
|
+
invar/shell/fs.py,sha256=wVD7DPWsCIJXuTyY_pi-5_LS82mXRdn_grJCOLn9zpU,3699
|
|
41
|
+
invar/shell/git.py,sha256=s6RQxEDQuLrmK3mru88EoYP8__4hiFW8AozlcxmY47E,2784
|
|
42
|
+
invar/shell/guard_helpers.py,sha256=QeYgbW0lgUa9Z_RCjAMG7UJdiMzz5cW48Lb2u-qgQi8,15114
|
|
43
|
+
invar/shell/guard_output.py,sha256=hcgamhSI781zUjHmrkNhYAjsy5qgEzCbxdtXuw1I-8w,11838
|
|
44
|
+
invar/shell/mcp_config.py,sha256=-hC7Y5BGuVs285b6gBARk7ZyzVxHwPgXSyt_GoN0jfs,4580
|
|
45
|
+
invar/shell/mutation.py,sha256=Lfyk2b8j8-hxAq-iwAgQeOhr7Ci6c5tRF1TXe3CxQCs,8914
|
|
46
|
+
invar/shell/property_tests.py,sha256=N9JreyH5PqR89oF5yLcX7ZAV-Koyg5BKo-J05-GUPsA,9109
|
|
47
|
+
invar/shell/subprocess_env.py,sha256=9oXl3eMEbzLsFEgMHqobEw6oW_wV0qMEP7pklwm58Pw,11453
|
|
48
|
+
invar/shell/template_engine.py,sha256=IzOiGsKVFo0lDUdtg27wMzIJJKToclv151RDZuDnHHo,11027
|
|
49
|
+
invar/shell/templates.py,sha256=l2En95E8jRVlojdQIqdZgRLVB43f_b1d_AJapKkozwA,15908
|
|
50
|
+
invar/shell/testing.py,sha256=PTrrCB0nIARuDQa_XREoRzbnqjXxju1l9Eb83pivH6c,10634
|
|
51
|
+
invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
|
|
52
|
+
invar/shell/commands/guard.py,sha256=0v4kfDyxIEl3iZiYjCeTwTgAEUnL-DvCKSvXaDwqRX8,17895
|
|
53
|
+
invar/shell/commands/init.py,sha256=K8nz1D3RqWPUr61-gYnMiSg-9cRnnYXPCR-4x37LMQA,17004
|
|
54
|
+
invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
|
|
55
|
+
invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
|
|
56
|
+
invar/shell/commands/perception.py,sha256=TyH_HpqyKkmE3-zcU4YyBG8ghwJaSFeRC-OQMVBDTbQ,3837
|
|
57
|
+
invar/shell/commands/sync_self.py,sha256=nmqBry7V2_enKwy2zzHg8UoedZNicLe3yKDhjmBeZ68,3880
|
|
58
|
+
invar/shell/commands/template_sync.py,sha256=g-IV4X1TGFVIypw9RBToexX7ZCSaCsD9yz-A0Sm-OmM,12155
|
|
59
|
+
invar/shell/commands/test.py,sha256=DKnlSbUydKq6skHLLKhZGg42qTAKtiLxrTU3oyFDmAo,4189
|
|
60
|
+
invar/shell/commands/update.py,sha256=0V5F8vxQ6PHPHPVYDmxdRD7xXeQEFypiJMYpY5ryiek,1349
|
|
61
|
+
invar/shell/prove/__init__.py,sha256=ZqlbmyMFJf6yAle8634jFuPRv8wNvHps8loMlOJyf8A,240
|
|
62
|
+
invar/shell/prove/accept.py,sha256=cnY_6jzU1EBnpLF8-zWUWcXiSXtCwxPsXEYXsSVPG38,3717
|
|
63
|
+
invar/shell/prove/cache.py,sha256=jbNdrvfLjvK7S0iqugErqeabb4YIbQuwIlcSRyCKbcg,4105
|
|
64
|
+
invar/shell/prove/crosshair.py,sha256=4Z_iIYBlkp-I6FqSYZa89wWB09V4Ouw2PduYhTn6rfw,16525
|
|
65
|
+
invar/shell/prove/hypothesis.py,sha256=QUclOOUg_VB6wbmHw8O2EPiL5qBOeBRqQeM04AVuLw0,9880
|
|
66
|
+
invar/templates/CLAUDE.md.template,sha256=W8o6M-wasCMg-7Ef2-RloDqvSKGS_9KUsy8DVbbUwDo,3719
|
|
67
|
+
invar/templates/__init__.py,sha256=cb3ht8KPK5oBn5oG6HsTznujmo9WriJ_P--fVxJwycc,45
|
|
68
|
+
invar/templates/aider.conf.yml.template,sha256=4xzSs3BXzFJvwdhnWbmzSY0yCbfx5oxqnV8ZjehqHBg,853
|
|
69
|
+
invar/templates/context.md.template,sha256=p1-iHl32Z30-FocJa7slcmoA7gtQrUrMO7cE_u5MrSo,2073
|
|
70
|
+
invar/templates/cursorrules.template,sha256=N6AiEJRJHGkHm2tswh3PnZ_07ozeyQQI8iEOGK5Aqoc,1023
|
|
71
|
+
invar/templates/manifest.toml,sha256=5JxaY9nl9hK5d1qWc2OJEGID6bFSX_Dp39lc5kRoIXk,4252
|
|
72
|
+
invar/templates/pre-commit-config.yaml.template,sha256=2qWY3E8iDUqi85jE_X7y0atE88YOlL5IZ93wkjCgQGo,1737
|
|
73
|
+
invar/templates/proposal.md.template,sha256=UP7SpQ7gk8jVlHGLQCSQ5c-kCj1DBQEz8M-vEStK77I,1573
|
|
74
|
+
invar/templates/commands/audit.md,sha256=eXBySlQrVyk054vYQWAZYzj-HgT2QXhpzziw6GlIeGM,4112
|
|
75
|
+
invar/templates/commands/guard.md,sha256=PyeAKfrmlXsgbrTDypQqXmTDKK1JHKhHEQrHqftA7X0,1177
|
|
76
|
+
invar/templates/config/CLAUDE.md.jinja,sha256=bowI-vhIHJvTCL20L4dwFtPODvkTWTllH2TRRVUf-cg,6295
|
|
77
|
+
invar/templates/config/context.md.jinja,sha256=sEQc-9jBqPpovlbnHSHcH7rwXVChfqDtUbYFTanStJI,2284
|
|
78
|
+
invar/templates/config/pre-commit.yaml.jinja,sha256=Qflmii8hngHciSgfa8mIlg3-E3D4b0xflm0-Q-cWcCc,1752
|
|
79
|
+
invar/templates/examples/README.md,sha256=xMcJZ1KEcfLJi5Ope_4FIbqDWKK3mRleAgllvgeNT6I,572
|
|
80
|
+
invar/templates/examples/conftest.py,sha256=uKA4NR7nyZWeSzY0URdZtw5zCcJpU32jNcaSKrI1Mxc,152
|
|
81
|
+
invar/templates/examples/contracts.py,sha256=uqJ6Y1GADo246MjFKoLY-_2E74cfBQsLO4vTqYcR81c,3241
|
|
82
|
+
invar/templates/examples/core_shell.py,sha256=ckor-7ZoaF7n8gVjnIO0CXlBjKGcVwmOGSDJbaXCSrM,4247
|
|
83
|
+
invar/templates/examples/workflow.md,sha256=jAopzQH1xE9_leJFdav4oj1AkI46-a2q4L8RQCkDknw,2334
|
|
84
|
+
invar/templates/protocol/INVAR.md,sha256=5u4q5PTVHHnJzRVEzfp4rXAfTHajIUNioRB4CQAs4JM,6662
|
|
85
|
+
invar/templates/skills/develop/SKILL.md.jinja,sha256=A0Hnx9FlBfPXdk2Mswx0zOnDZbgszI3u5MvLVd2rlM4,9544
|
|
86
|
+
invar/templates/skills/investigate/SKILL.md.jinja,sha256=vY6Hri5Nsy9C0JsLWU64NUitrPnBx49NtG0d4cg_FgI,3058
|
|
87
|
+
invar/templates/skills/propose/SKILL.md.jinja,sha256=b_cw_EiAneSGSOMTJWu_j9UwIlW7h-xab39uQgFb5wY,2734
|
|
88
|
+
invar/templates/skills/review/SKILL.md.jinja,sha256=hkLs5BHlzKNat4BdXNfgB3-3Bvz7C6gFEQJq-0hQHSk,3679
|
|
89
|
+
invar_tools-1.3.0.dist-info/METADATA,sha256=Gwg9kdiPndJzHV5168zGSGKXw9O1IXvIEbIgCashDjQ,11656
|
|
90
|
+
invar_tools-1.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
91
|
+
invar_tools-1.3.0.dist-info/entry_points.txt,sha256=xjkp8_Kipb6KJR6VNfkAEqiOpvrwUnwUG53cegBA6pQ,57
|
|
92
|
+
invar_tools-1.3.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
|
|
93
|
+
invar_tools-1.3.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
|
|
94
|
+
invar_tools-1.3.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
|
|
95
|
+
invar_tools-1.3.0.dist-info/RECORD,,
|
invar/contracts.py
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Composable contracts for Invar.
|
|
3
|
-
|
|
4
|
-
Provides Contract class with &, |, ~ operators for combining conditions,
|
|
5
|
-
and a standard library of common predicates. Works with deal decorators.
|
|
6
|
-
|
|
7
|
-
Inspired by Idris' dependent types.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from typing import TYPE_CHECKING, Any
|
|
14
|
-
|
|
15
|
-
import deal
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from collections.abc import Callable
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class Contract:
|
|
23
|
-
"""
|
|
24
|
-
Composable contract with &, |, ~ operators.
|
|
25
|
-
|
|
26
|
-
Contracts encapsulate predicates that can be combined and reused.
|
|
27
|
-
Works with deal.pre for runtime checking.
|
|
28
|
-
|
|
29
|
-
Examples:
|
|
30
|
-
>>> NonEmpty = Contract(lambda x: len(x) > 0, "non-empty")
|
|
31
|
-
>>> Sorted = Contract(lambda x: list(x) == sorted(x), "sorted")
|
|
32
|
-
>>> combined = NonEmpty & Sorted
|
|
33
|
-
>>> combined.check([1, 2, 3])
|
|
34
|
-
True
|
|
35
|
-
>>> combined.check([])
|
|
36
|
-
False
|
|
37
|
-
>>> combined.check([3, 1, 2])
|
|
38
|
-
False
|
|
39
|
-
>>> (NonEmpty | Sorted).check([]) # Empty but sorted
|
|
40
|
-
True
|
|
41
|
-
>>> (~NonEmpty).check([]) # NOT non-empty
|
|
42
|
-
True
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
predicate: Callable[[Any], bool]
|
|
46
|
-
description: str
|
|
47
|
-
|
|
48
|
-
def check(self, value: Any) -> bool:
|
|
49
|
-
"""Check if value satisfies the contract."""
|
|
50
|
-
return self.predicate(value)
|
|
51
|
-
|
|
52
|
-
def __and__(self, other: Contract) -> Contract:
|
|
53
|
-
"""Combine contracts with AND."""
|
|
54
|
-
return Contract(
|
|
55
|
-
predicate=lambda x: self.check(x) and other.check(x),
|
|
56
|
-
description=f"({self.description} AND {other.description})",
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
def __or__(self, other: Contract) -> Contract:
|
|
60
|
-
"""Combine contracts with OR."""
|
|
61
|
-
return Contract(
|
|
62
|
-
predicate=lambda x: self.check(x) or other.check(x),
|
|
63
|
-
description=f"({self.description} OR {other.description})",
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
def __invert__(self) -> Contract:
|
|
67
|
-
"""Negate the contract."""
|
|
68
|
-
return Contract(
|
|
69
|
-
predicate=lambda x: not self.check(x),
|
|
70
|
-
description=f"NOT({self.description})",
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
def __call__(self, *args: Any, **kwargs: Any) -> bool:
|
|
74
|
-
"""Allow using as deal.pre predicate directly."""
|
|
75
|
-
value = args[0] if args else next(iter(kwargs.values()))
|
|
76
|
-
return self.check(value)
|
|
77
|
-
|
|
78
|
-
def __repr__(self) -> str:
|
|
79
|
-
return f"Contract({self.description!r})"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def pre(*contracts: Contract) -> Callable[[Callable], Callable]:
|
|
83
|
-
"""
|
|
84
|
-
Decorator accepting Contract objects for preconditions.
|
|
85
|
-
|
|
86
|
-
Works with deal.pre under the hood.
|
|
87
|
-
|
|
88
|
-
Examples:
|
|
89
|
-
>>> from invar.contracts import pre, NonEmpty
|
|
90
|
-
>>> @pre(NonEmpty)
|
|
91
|
-
... def first(xs): return xs[0]
|
|
92
|
-
>>> first([1, 2, 3])
|
|
93
|
-
1
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
def combined(*args: Any, **kwargs: Any) -> bool:
|
|
97
|
-
value = args[0] if args else next(iter(kwargs.values()))
|
|
98
|
-
return all(c.check(value) for c in contracts)
|
|
99
|
-
|
|
100
|
-
return deal.pre(combined)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def post(*contracts: Contract) -> Callable[[Callable], Callable]:
|
|
104
|
-
"""
|
|
105
|
-
Decorator accepting Contract objects for postconditions.
|
|
106
|
-
|
|
107
|
-
Works with deal.post under the hood.
|
|
108
|
-
|
|
109
|
-
Examples:
|
|
110
|
-
>>> from invar.contracts import post, NonEmpty
|
|
111
|
-
>>> @post(NonEmpty)
|
|
112
|
-
... def get_list(): return [1]
|
|
113
|
-
>>> get_list()
|
|
114
|
-
[1]
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
def combined(result: Any) -> bool:
|
|
118
|
-
return all(c.check(result) for c in contracts)
|
|
119
|
-
|
|
120
|
-
return deal.post(combined)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# =============================================================================
|
|
124
|
-
# Standard Library of Contracts
|
|
125
|
-
# =============================================================================
|
|
126
|
-
|
|
127
|
-
# --- Collections ---
|
|
128
|
-
NonEmpty: Contract = Contract(lambda x: len(x) > 0, "non-empty")
|
|
129
|
-
Sorted: Contract = Contract(lambda x: list(x) == sorted(x), "sorted")
|
|
130
|
-
Unique: Contract = Contract(lambda x: len(x) == len(set(x)), "unique")
|
|
131
|
-
SortedNonEmpty: Contract = NonEmpty & Sorted
|
|
132
|
-
|
|
133
|
-
# --- Numbers ---
|
|
134
|
-
Positive: Contract = Contract(lambda x: x > 0, "positive")
|
|
135
|
-
NonNegative: Contract = Contract(lambda x: x >= 0, "non-negative")
|
|
136
|
-
Negative: Contract = Contract(lambda x: x < 0, "negative")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def InRange(lo: float, hi: float) -> Contract:
|
|
140
|
-
"""Create a contract checking value is in [lo, hi]."""
|
|
141
|
-
return Contract(lambda x: lo <= x <= hi, f"[{lo},{hi}]")
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Percentage: Contract = InRange(0, 100)
|
|
145
|
-
|
|
146
|
-
# --- Strings ---
|
|
147
|
-
NonBlank: Contract = Contract(lambda s: bool(s and s.strip()), "non-blank")
|
|
148
|
-
|
|
149
|
-
# --- Lists with elements ---
|
|
150
|
-
AllPositive: Contract = Contract(lambda xs: all(x > 0 for x in xs), "all positive")
|
|
151
|
-
AllNonNegative: Contract = Contract(lambda xs: all(x >= 0 for x in xs), "all non-negative")
|
|
152
|
-
NoNone: Contract = Contract(lambda xs: None not in xs, "no None")
|