tooli 1.0.1__tar.gz

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.
Files changed (76) hide show
  1. tooli-1.0.1/LICENSE +21 -0
  2. tooli-1.0.1/PKG-INFO +34 -0
  3. tooli-1.0.1/README.md +317 -0
  4. tooli-1.0.1/pyproject.toml +71 -0
  5. tooli-1.0.1/setup.cfg +4 -0
  6. tooli-1.0.1/tests/test_api.py +27 -0
  7. tooli-1.0.1/tests/test_app.py +649 -0
  8. tooli-1.0.1/tests/test_architecture.py +113 -0
  9. tooli-1.0.1/tests/test_auth.py +63 -0
  10. tooli-1.0.1/tests/test_context.py +79 -0
  11. tooli-1.0.1/tests/test_csvkit_t.py +106 -0
  12. tooli-1.0.1/tests/test_docq.py +134 -0
  13. tooli-1.0.1/tests/test_docs.py +59 -0
  14. tooli-1.0.1/tests/test_dry_run.py +42 -0
  15. tooli-1.0.1/tests/test_envar.py +115 -0
  16. tooli-1.0.1/tests/test_eval.py +89 -0
  17. tooli-1.0.1/tests/test_gitsum.py +112 -0
  18. tooli-1.0.1/tests/test_idempotency.py +60 -0
  19. tooli-1.0.1/tests/test_imgsort.py +140 -0
  20. tooli-1.0.1/tests/test_mcp.py +70 -0
  21. tooli-1.0.1/tests/test_note_indexer.py +159 -0
  22. tooli-1.0.1/tests/test_otel.py +101 -0
  23. tooli-1.0.1/tests/test_proj.py +100 -0
  24. tooli-1.0.1/tests/test_secret_input.py +104 -0
  25. tooli-1.0.1/tests/test_security.py +106 -0
  26. tooli-1.0.1/tests/test_skill.py +24 -0
  27. tooli-1.0.1/tests/test_syswatch.py +61 -0
  28. tooli-1.0.1/tests/test_taskr.py +119 -0
  29. tooli-1.0.1/tests/test_telemetry.py +87 -0
  30. tooli-1.0.1/tests/test_versioning.py +101 -0
  31. tooli-1.0.1/tooli/__init__.py +27 -0
  32. tooli-1.0.1/tooli/annotations.py +29 -0
  33. tooli-1.0.1/tooli/api/openapi.py +94 -0
  34. tooli-1.0.1/tooli/api/server.py +112 -0
  35. tooli-1.0.1/tooli/app.py +378 -0
  36. tooli-1.0.1/tooli/auth.py +32 -0
  37. tooli-1.0.1/tooli/command.py +1226 -0
  38. tooli-1.0.1/tooli/command_meta.py +55 -0
  39. tooli-1.0.1/tooli/config.py +63 -0
  40. tooli-1.0.1/tooli/context.py +118 -0
  41. tooli-1.0.1/tooli/docs/llms_txt.py +40 -0
  42. tooli-1.0.1/tooli/docs/man.py +67 -0
  43. tooli-1.0.1/tooli/docs/skill.py +105 -0
  44. tooli-1.0.1/tooli/dry_run.py +98 -0
  45. tooli-1.0.1/tooli/envelope.py +25 -0
  46. tooli-1.0.1/tooli/errors.py +175 -0
  47. tooli-1.0.1/tooli/eval/__init__.py +23 -0
  48. tooli-1.0.1/tooli/eval/analyzer.py +119 -0
  49. tooli-1.0.1/tooli/eval/recorder.py +139 -0
  50. tooli-1.0.1/tooli/exit_codes.py +25 -0
  51. tooli-1.0.1/tooli/idempotency.py +74 -0
  52. tooli-1.0.1/tooli/input.py +225 -0
  53. tooli-1.0.1/tooli/mcp/__init__.py +6 -0
  54. tooli-1.0.1/tooli/mcp/export.py +69 -0
  55. tooli-1.0.1/tooli/mcp/server.py +59 -0
  56. tooli-1.0.1/tooli/output.py +91 -0
  57. tooli-1.0.1/tooli/pagination.py +75 -0
  58. tooli-1.0.1/tooli/providers/__init__.py +7 -0
  59. tooli-1.0.1/tooli/providers/base.py +18 -0
  60. tooli-1.0.1/tooli/providers/filesystem.py +86 -0
  61. tooli-1.0.1/tooli/providers/local.py +32 -0
  62. tooli-1.0.1/tooli/py.typed +0 -0
  63. tooli-1.0.1/tooli/schema.py +101 -0
  64. tooli-1.0.1/tooli/security/__init__.py +7 -0
  65. tooli-1.0.1/tooli/security/policy.py +24 -0
  66. tooli-1.0.1/tooli/security/sanitizer.py +35 -0
  67. tooli-1.0.1/tooli/telemetry.py +94 -0
  68. tooli-1.0.1/tooli/telemetry_pipeline.py +257 -0
  69. tooli-1.0.1/tooli/testing.py +39 -0
  70. tooli-1.0.1/tooli/transforms.py +80 -0
  71. tooli-1.0.1/tooli/versioning.py +103 -0
  72. tooli-1.0.1/tooli.egg-info/PKG-INFO +34 -0
  73. tooli-1.0.1/tooli.egg-info/SOURCES.txt +74 -0
  74. tooli-1.0.1/tooli.egg-info/dependency_links.txt +1 -0
  75. tooli-1.0.1/tooli.egg-info/requires.txt +19 -0
  76. tooli-1.0.1/tooli.egg-info/top_level.txt +1 -0
tooli-1.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tooli developers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tooli-1.0.1/PKG-INFO ADDED
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: tooli
3
+ Version: 1.0.1
4
+ Summary: The agent-native CLI framework for Python
5
+ Author: tooli developers
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/weisberg/tooli
8
+ Project-URL: Repository, https://github.com/weisberg/tooli
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: typer>=0.9
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Dist: rich>=13.0
23
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
27
+ Requires-Dist: ruff>=0.4; extra == "dev"
28
+ Requires-Dist: mypy>=1.0; extra == "dev"
29
+ Provides-Extra: mcp
30
+ Requires-Dist: fastmcp>=2.0; extra == "mcp"
31
+ Provides-Extra: api
32
+ Requires-Dist: starlette>=0.27; extra == "api"
33
+ Requires-Dist: uvicorn>=0.20; extra == "api"
34
+ Dynamic: license-file
tooli-1.0.1/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # Tooli
2
+
3
+ [![CI](https://github.com/weisberg/tooli/actions/workflows/ci.yml/badge.svg)](https://github.com/weisberg/tooli/actions/workflows/ci.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/tooli)](https://pypi.org/project/tooli/)
5
+ [![Python 3.10+](https://img.shields.io/pypi/pyversions/tooli)](https://pypi.org/project/tooli/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ The agent-native CLI framework for Python. Write one function, get a CLI, an MCP tool, and a self-documenting schema.
9
+
10
+ The name comes from "tool" + "CLI" = "tooli".
11
+
12
+ Tooli turns typed Python functions into CLI commands that are simultaneously human-friendly (Rich output, shell completions) and machine-consumable (JSON schemas, structured output, MCP compatibility). No separate "agent version" of your tool required.
13
+
14
+ ## Why Tooli?
15
+
16
+ AI agents invoke thousands of CLI commands daily, but standard CLIs were designed for humans:
17
+
18
+ - **Interactive prompts hang agents** that can't navigate pagers or password dialogs
19
+ - **Unstructured output wastes tokens** — agents parse text with regex instead of reading JSON
20
+ - **Vague errors prevent self-correction** — "Error: invalid input" gives agents nothing to work with
21
+ - **No discoverability** — agents hallucinate flags for undocumented tools
22
+
23
+ Tooli fixes all of this. One decorated function produces a CLI, a JSON Schema, an MCP tool definition, structured errors with recovery suggestions, and auto-generated documentation — from a single source of truth.
24
+
25
+ ## Features
26
+
27
+ - **Dual-mode output** — Rich tables for humans, JSON/JSONL for agents, auto-detected via TTY
28
+ - **Structured errors** — actionable error objects with suggestion fields that guide agent self-correction
29
+ - **Schema generation** — JSON Schema from type hints, compatible with MCP `inputSchema` and OpenAI function-calling
30
+ - **MCP server mode** — serve your CLI as an MCP tool server over stdio or HTTP with zero extra code
31
+ - **SKILL.md generation** — auto-generated agent-readable documentation, always in sync with code
32
+ - **stdin/file parity** — `StdinOr[T]` type makes files, URLs, and piped data interchangeable
33
+ - **Standard global flags** — `--json`, `--jsonl`, `--plain`, `--quiet`, `--dry-run`, `--schema` injected automatically
34
+ - **Agent-safe by default** — no interactive prompts in non-TTY mode, no stdout pollution, strict exit codes
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install tooli
40
+ ```
41
+
42
+ Optional extras:
43
+
44
+ ```bash
45
+ pip install tooli[mcp] # MCP server support (fastmcp)
46
+ pip install tooli[api] # HTTP API server (starlette, uvicorn)
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from tooli import Tooli, Annotated, Option, Argument
53
+ from tooli.annotations import ReadOnly, Idempotent
54
+ from pathlib import Path
55
+
56
+ app = Tooli(
57
+ name="file-tools",
58
+ description="File manipulation utilities",
59
+ version="1.0.0",
60
+ )
61
+
62
+ @app.command(
63
+ annotations=ReadOnly | Idempotent,
64
+ examples=[
65
+ {"args": ["--pattern", "*.py", "--root", "/project"],
66
+ "description": "Find all Python files in a project"},
67
+ ],
68
+ )
69
+ def find_files(
70
+ pattern: Annotated[str, Argument(help="Glob pattern to match files")],
71
+ root: Annotated[Path, Option(help="Root directory to search from")] = Path("."),
72
+ max_depth: Annotated[int, Option(help="Maximum directory depth")] = 10,
73
+ ) -> list[dict]:
74
+ """Find files matching a glob pattern in a directory tree."""
75
+ results = []
76
+ for path in root.rglob(pattern):
77
+ results.append({"path": str(path), "size": path.stat().st_size})
78
+ return results
79
+
80
+ if __name__ == "__main__":
81
+ app()
82
+ ```
83
+
84
+ ### Human usage
85
+
86
+ ```bash
87
+ $ file-tools find-files "*.py" --root ./src
88
+ ┌──────────────────────┬───────┐
89
+ │ Path │ Size │
90
+ ├──────────────────────┼───────┤
91
+ │ src/main.py │ 1,204 │
92
+ │ src/utils.py │ 892 │
93
+ └──────────────────────┴───────┘
94
+ ```
95
+
96
+ ### Agent usage
97
+
98
+ ```bash
99
+ $ file-tools find-files "*.py" --root ./src --json
100
+ {
101
+ "ok": true,
102
+ "result": [
103
+ {"path": "src/main.py", "size": 1204},
104
+ {"path": "src/utils.py", "size": 892}
105
+ ],
106
+ "meta": {"tool": "file-tools.find-files", "version": "1.0.0", "duration_ms": 34}
107
+ }
108
+ ```
109
+
110
+ ### Schema export
111
+
112
+ ```bash
113
+ $ file-tools find-files --schema
114
+ {
115
+ "name": "find-files",
116
+ "description": "Find files matching a glob pattern in a directory tree.",
117
+ "inputSchema": {
118
+ "type": "object",
119
+ "properties": {
120
+ "pattern": {"type": "string", "description": "Glob pattern to match files"},
121
+ "root": {"type": "string", "default": ".", "description": "Root directory to search from"},
122
+ "max_depth": {"type": "integer", "default": 10, "description": "Maximum directory depth"}
123
+ },
124
+ "required": ["pattern"]
125
+ },
126
+ "annotations": {"readOnlyHint": true, "idempotentHint": true}
127
+ }
128
+ ```
129
+
130
+ ### MCP server mode
131
+
132
+ ```bash
133
+ $ file-tools mcp serve --transport stdio
134
+ $ file-tools mcp serve --transport http --host 127.0.0.1 --port 8080
135
+ ```
136
+
137
+ Add to your MCP client config and every command becomes a tool.
138
+
139
+ For SSE-enabled clients, use:
140
+
141
+ ```bash
142
+ $ file-tools mcp serve --transport sse --host 127.0.0.1 --port 8080
143
+ ```
144
+
145
+ ## Structured Errors
146
+
147
+ When something goes wrong, agents get actionable recovery guidance instead of opaque messages:
148
+
149
+ ```bash
150
+ $ file-tools find-files "*.rs" --root ./src --json
151
+ {
152
+ "ok": false,
153
+ "error": {
154
+ "code": "E3001",
155
+ "category": "state",
156
+ "message": "No files matched pattern '*.rs' in ./src",
157
+ "suggestion": {
158
+ "action": "retry_with_modified_input",
159
+ "fix": "The directory contains .py files. Try pattern '*.py' instead.",
160
+ "example": "find-files '*.py' --root ./src"
161
+ },
162
+ "is_retryable": true
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## Output Modes
168
+
169
+ Tooli auto-detects the right output format, or you can be explicit:
170
+
171
+ | Flag | Behavior |
172
+ |---|---|
173
+ | *(TTY, no flag)* | Rich formatted output for humans |
174
+ | `--json` | Single JSON envelope to stdout |
175
+ | `--jsonl` | Newline-delimited JSON for streaming |
176
+ | `--plain` | Unformatted text for grep/awk pipelines |
177
+ | `--quiet` | Suppress non-essential output |
178
+
179
+ ## Optional Telemetry
180
+
181
+ Tooli supports anonymous, opt-in usage telemetry for tool authors.
182
+
183
+ - Disabled by default.
184
+ - Enable with `TOOLI_TELEMETRY=true` or `Tooli(telemetry=True)`.
185
+ - Records only command identifiers, duration (ms), success/failure outcome, and exit/error codes.
186
+ - No command arguments or command output payloads are recorded.
187
+ - Local-first by default to `~/.config/tooli/telemetry/events.jsonl`.
188
+ - Remote forwarding is opt-in via `TOOLI_TELEMETRY_ENDPOINT` or `Tooli(telemetry_endpoint=...)`.
189
+ - Retention policy is local-only by default: entries older than 30 days are pruned.
190
+
191
+ ## Dry-Run Planning
192
+
193
+ Tooli supports opt-in dry-run planning via `@dry_run_support`.
194
+
195
+ - Decorate command functions with `@dry_run_support`.
196
+ - Use `record_dry_action(action, target, details={...})` to add plan steps.
197
+ - Invoke with `--dry-run` to return the action plan instead of executing side effects.
198
+ - In JSON output, dry-run responses set `meta.dry_run` to `true`.
199
+
200
+ ## Auto-Generated Documentation
201
+
202
+ ```bash
203
+ # Agent-readable skill documentation
204
+ $ file-tools generate-skill > SKILL.md
205
+
206
+ # LLM-friendly docs (llms.txt standard)
207
+ $ file-tools docs llms
208
+
209
+ # Unix man page
210
+ $ file-tools docs man
211
+ ```
212
+
213
+ ## Input Unification
214
+
215
+ The `StdinOr[T]` type makes files, URLs, and piped data interchangeable:
216
+
217
+ ```python
218
+ from tooli import StdinOr
219
+
220
+ @app.command()
221
+ def process(
222
+ input_data: Annotated[StdinOr[Path], Argument(help="Input file, URL, or stdin")],
223
+ ) -> dict:
224
+ """Process data from any input source."""
225
+ ...
226
+ ```
227
+
228
+ ```bash
229
+ # All equivalent:
230
+ $ file-tools process data.csv
231
+ $ file-tools process https://example.com/data.csv
232
+ $ cat data.csv | file-tools process -
233
+ ```
234
+
235
+ ## Global Flags
236
+
237
+ Every Tooli command automatically gets:
238
+
239
+ ```
240
+ --output, -o auto|json|jsonl|text|plain
241
+ --json/--jsonl Convenience aliases
242
+ --quiet, -q Suppress non-essential output
243
+ --verbose, -v Increase verbosity (-vvv)
244
+ --dry-run Preview without executing
245
+ --yes Skip confirmation prompts (for automation/agents)
246
+ --no-color Disable colors (also respects NO_COLOR)
247
+ --print0 Emit NUL-separated output for list types in text/plain modes
248
+ --timeout Max execution time in seconds
249
+ --null Parse NUL-delimited list input from stdin (list-processing)
250
+ --schema Print JSON Schema and exit
251
+ --response-format concise|detailed
252
+ --help-agent Token-optimized help for agents
253
+ ```
254
+
255
+ ## Architecture
256
+
257
+ Tooli builds on the Python typing + decorator pipeline, adding a parallel schema generation path:
258
+
259
+ ```
260
+ @app.command()
261
+ Python function + type hints
262
+ │ │
263
+ ▼ ▼
264
+ CLI Pipeline Schema Pipeline
265
+ → CLI params → Pydantic model
266
+ → CLI parser → JSON Schema
267
+ │ │
268
+ ▼ ▼
269
+ CLI Output Agent Output
270
+ Rich tables MCP tool schema
271
+ Completions SKILL.md / JSON
272
+ ```
273
+
274
+ Key design decisions:
275
+ - **Library-first API** — the public surface is Tooli-native (no framework objects leaked into user code)
276
+ - **Pydantic schemas** — same pipeline as FastAPI and FastMCP
277
+ - **Functions stay callable** — no mutation; test with `CliRunner` or call directly as Python
278
+
279
+ ## Examples
280
+
281
+ The [`examples/`](examples/) directory contains 18 complete CLI apps built with Tooli, each demonstrating different features:
282
+
283
+ | App | Features |
284
+ |---|---|
285
+ | **[docq](examples/docq/)** | ReadOnly, paginated, stdin input, output formats |
286
+ | **[gitsum](examples/gitsum/)** | ReadOnly, subprocess, StdinOr for diffs |
287
+ | **[csvkit_t](examples/csvkit_t/)** | StdinOr, JSONL output, paginated, OpenWorld |
288
+ | **[syswatch](examples/syswatch/)** | ReadOnly, paginated, structured errors |
289
+ | **[taskr](examples/taskr/)** | Idempotent, Destructive, paginated CRUD |
290
+ | **[proj](examples/proj/)** | Destructive, DryRunRecorder, Idempotent |
291
+ | **[envar](examples/envar/)** | SecretInput, AuthContext scopes |
292
+ | **[imgsort](examples/imgsort/)** | Destructive+Idempotent, DryRunRecorder, batch ops |
293
+ | **[note_indexer](examples/note_indexer/)** | ReadOnly, paginated, JSON index, error handling |
294
+
295
+ See the [examples README](examples/README.md) for the full list and usage guide.
296
+
297
+ ## Development
298
+
299
+ ```bash
300
+ # Clone and install for development
301
+ git clone https://github.com/weisberg/tooli.git
302
+ cd tooli
303
+ pip install -e ".[dev]"
304
+
305
+ # Run tests
306
+ pytest
307
+
308
+ # Lint and type check
309
+ ruff check .
310
+ mypy tooli
311
+ ```
312
+
313
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
314
+
315
+ ## License
316
+
317
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tooli"
7
+ version = "1.0.1"
8
+ description = "The agent-native CLI framework for Python"
9
+ readme = "pypi/pypi_project.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "tooli developers"}
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "typer>=0.9",
27
+ "pydantic>=2.0",
28
+ "rich>=13.0",
29
+ "tomli>=2.0; python_version < '3.11'",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=7.0",
35
+ "pytest-cov>=4.0",
36
+ "ruff>=0.4",
37
+ "mypy>=1.0",
38
+ ]
39
+ mcp = [
40
+ "fastmcp>=2.0",
41
+ ]
42
+ api = [
43
+ "starlette>=0.27",
44
+ "uvicorn>=0.20",
45
+ ]
46
+
47
+ [tool.setuptools.packages.find]
48
+ include = ["tooli*"]
49
+
50
+ [project.urls]
51
+ Homepage = "https://github.com/weisberg/tooli"
52
+ Repository = "https://github.com/weisberg/tooli"
53
+
54
+ [tool.ruff]
55
+ line-length = 88
56
+ target-version = "py310"
57
+
58
+ [tool.ruff.lint]
59
+ select = ["E", "W", "F", "I", "UP", "B", "SIM", "TCH"]
60
+
61
+ [tool.mypy]
62
+ python_version = "3.10"
63
+ strict = true
64
+ warn_return_any = true
65
+ warn_unused_configs = true
66
+ warn_unused_ignores = false
67
+ disallow_untyped_defs = true
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ addopts = "-v --tb=short"
tooli-1.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ """Tests for HTTP API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from tooli import Tooli
8
+ from tooli.testing import TooliTestClient
9
+
10
+
11
+ def test_api_export_openapi() -> None:
12
+ """api export-openapi should output valid OpenAPI schema."""
13
+ app = Tooli(name="test-api")
14
+
15
+ @app.command()
16
+ def compute(x: int, y: int) -> int:
17
+ """Add two numbers."""
18
+ return x + y
19
+
20
+ client = TooliTestClient(app)
21
+ result = client.invoke(["api", "export-openapi"])
22
+ assert result.exit_code == 0
23
+
24
+ schema = json.loads(result.output)
25
+ assert schema["openapi"] == "3.1.0"
26
+ assert "/compute" in schema["paths"]
27
+ assert "x" in schema["paths"]["/compute"]["post"]["requestBody"]["content"]["application/json"]["schema"]["properties"]