hydroid-cli 0.2.0__py3-none-win_amd64.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,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: hydroid-cli
3
+ Version: 0.2.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Environment :: Console
6
+ Classifier: Framework :: FastAPI
7
+ Classifier: Framework :: AsyncIO
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Rust
10
+ Classifier: Topic :: Software Development :: Quality Assurance
11
+ License-File: LICENSE
12
+ License-File: THIRD_PARTY_NOTICES.md
13
+ Summary: Finds blocking calls reachable on the event loop in async FastAPI code.
14
+ Keywords: fastapi,asyncio,static-analysis,linter,event-loop
15
+ License-Expression: MIT
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
18
+ Project-URL: Documentation, https://github.com/agusmdev/hydroid/blob/main/docs/GUIDE.md
19
+ Project-URL: Repository, https://github.com/agusmdev/hydroid
20
+
21
+ # hydroid
22
+
23
+ Finds blocking calls that run on the event loop in async FastAPI (and any asyncio) code — at any
24
+ call depth, across modules, through callbacks, decorators, context managers, properties and
25
+ subclass overrides — and shows the whole chain.
26
+
27
+ ```
28
+ error[blocking-http]: `requests.api.get` blocks the event loop
29
+ --> app/main.py:12:12 in `app.main.get_user`
30
+ = reached from route GET /users/{user_id}: app.main.get_user (app/main.py:11)
31
+ = blocking chain (3 calls):
32
+ app/main.py:12:12 services.load -> app.services.load
33
+ app/services.py:7:22 fetch -> app.repo.fetch
34
+ app/repo.py:5:16 http_get -> requests.api.get
35
+ = help: use an async client (`httpx.AsyncClient`, `aiohttp`) or `await asyncio.to_thread(...)`
36
+ ```
37
+
38
+ **New here? Read the [usage guide](docs/GUIDE.md).** Adding it to CI, or letting a coding agent
39
+ set it up: [CI/CD and agent setup](#cicd-and-agent-setup).
40
+
41
+ Written in Rust on top of [ty](https://github.com/astral-sh/ty)'s semantic model (types, imports,
42
+ go-to-definition). Polar's server (1,854 files, 108k call sites) takes about as long as ty needs
43
+ to type-check it the first time (~10 s on 4 vCPUs), then ~0.35 s while nothing changed: extracted
44
+ facts are cached in `.hydroid_cache/`. `HYDROID_TIMINGS=1` prints where the time goes.
45
+
46
+ ## Install and run
47
+
48
+ With [uv](https://docs.astral.sh/uv/), using the prebuilt binaries of the latest release
49
+ (macOS, Linux, Windows; no Rust needed):
50
+
51
+ ```sh
52
+ uvx --from hydroid-cli --find-links https://github.com/agusmdev/hydroid/releases/expanded_assets/v0.1.0 hydroid .
53
+ ```
54
+
55
+ Or straight from the source on `main` (builds once with a Rust toolchain, then cached):
56
+
57
+ ```sh
58
+ uvx --from git+https://github.com/agusmdev/hydroid hydroid .
59
+ ```
60
+
61
+ To install the `hydroid` command permanently, replace `uvx --from` with `uv tool install`
62
+ (e.g. `uv tool install git+https://github.com/agusmdev/hydroid`), then run `hydroid .` anywhere.
63
+ From a clone: `cargo install --path crates/hydroid`. More in the [guide](docs/GUIDE.md#1-install).
64
+
65
+ ```sh
66
+ hydroid path/to/project # finds .venv / VIRTUAL_ENV like ty
67
+ hydroid . --python .venv --format sarif > hydroid.sarif
68
+ ```
69
+
70
+ | Flag | |
71
+ |---|---|
72
+ | `--python PATH` | virtualenv or interpreter to resolve imports (default: discovered) |
73
+ | `--follow-libs` | also analyze third-party function bodies |
74
+ | `--cpu` | also report CPU-bound calls (bcrypt, pbkdf2, scrypt) |
75
+ | `--strict` | report calls on the loop whose target is unknown, and fail on them |
76
+ | `--exclude GLOB` | skip files (repeatable) |
77
+ | `--no-cache` | do not read or write `.hydroid_cache/` |
78
+ | `--format human\|json\|sarif` | output |
79
+
80
+ Exit status: `0` clean, `1` findings (or unresolved calls with `--strict`), `2` error.
81
+ Suppress a line with `# hydroid: ignore`.
82
+
83
+ Configuration in `pyproject.toml` (flags win):
84
+
85
+ ```toml
86
+ [tool.hydroid]
87
+ python = ".venv"
88
+ exclude = ["migrations/**"]
89
+ strict = false
90
+
91
+ [[tool.hydroid.sink]] # your own blocking functions (defining qualified names)
92
+ category = "sdk"
93
+ advice = "use the async client"
94
+ functions = ["acme.client.Client.fetch"]
95
+ ```
96
+
97
+ ## CI/CD and agent setup
98
+
99
+ hydroid exits `1` when it finds blocking calls, so it can gate a build as is. On GitHub,
100
+ `--format sarif` puts each blocking chain on the pull request through code scanning:
101
+
102
+ ```yaml
103
+ # .github/workflows/hydroid.yml
104
+ name: hydroid
105
+ on: [push, pull_request]
106
+ jobs:
107
+ hydroid:
108
+ runs-on: ubuntu-latest
109
+ permissions:
110
+ security-events: write
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: astral-sh/setup-uv@v6
114
+ - run: uv sync # hydroid needs the project's dependencies installed
115
+ - run: >-
116
+ uvx --from hydroid-cli --find-links https://github.com/agusmdev/hydroid/releases/expanded_assets/v0.1.0
117
+ hydroid . --format sarif > hydroid.sarif || [ $? -eq 1 ]
118
+ - uses: github/codeql-action/upload-sarif@v3
119
+ with:
120
+ sarif_file: hydroid.sarif
121
+ ```
122
+
123
+ `|| [ $? -eq 1 ]` reports findings without failing the job (exit `2`, an error, still fails it);
124
+ drop it and the upload step to fail the build instead.
125
+
126
+ [docs/AGENT_SETUP.md](docs/AGENT_SETUP.md) has:
127
+
128
+ - **a prompt to paste into a coding agent** (Claude Code, Cursor, Codex…) that installs hydroid
129
+ in your repository, configures `[tool.hydroid]`, triages the existing findings (fix,
130
+ `# hydroid: ignore` with a reason, or report to you) and adds the CI job;
131
+ - configs for GitLab CI, pre-commit, monorepos, projects not using uv, and other CI systems;
132
+ - a path for adopting it on a code base with existing findings: report-only first, then gate.
133
+
134
+ ## How it works
135
+
136
+ 1. **Facts** (`crates/hydroid_ty`, the only crate that touches ty): every function body is walked
137
+ in parallel; each call is resolved to its definitions through ty (types, then
138
+ go-to-definition), including implicit calls (`with`, `for` and comprehensions, properties
139
+ and their setters, descriptors, operators like `x[k]` and `k in x` on user classes,
140
+ `__getattr__`, iterators' `__next__`, constructors with `__post_init__` and pydantic
141
+ validators, builtins and `contextlib` helpers that iterate, enter or call their arguments like
142
+ `sorted(xs, key=f)` or `stack.enter_context(cm)`, `singledispatch` implementations). Callables passed as arguments are recorded as flows
143
+ (`to_thread(f)`, `Depends(dep)`, `on_startup=[f]`, decorators), and so are callables stored
144
+ on `self` and returned by functions. When ty does not know a value's type because it comes
145
+ from an unannotated function, the class that function returns is used. The facts of an unchanged project are reused from `.hydroid_cache/` (keyed by the
146
+ binary, the options, the installed packages and every source file's contents).
147
+ 2. **Analysis** (`crates/hydroid_core`, plain graph code): where code runs is a property of the
148
+ call, so "does calling `f` block?" is computed once per function by a backwards BFS from
149
+ blocking functions — shortest witness chains, no recursion, any depth. Diagnostics are the
150
+ call sites inside loop code (`async def` bodies, sync callbacks scheduled on the loop) whose
151
+ callee blocks. Offload APIs cut the graph; callbacks are attributed to the call site passing
152
+ them; overrides are found by class-hierarchy analysis (test doubles only stand in for real
153
+ classes in test code); calling a generator function runs nothing until the generator is
154
+ iterated; a decorated function is reached through its decorator's wrappers only when they call
155
+ it. Starting an event loop (`asyncio.run`) is only reported when loop code does it directly:
156
+ sync bridges reaching it almost always check for a running loop first.
157
+ 3. **Catalog** (`crates/hydroid_core/catalog.toml`): blocking functions, offload APIs, loop
158
+ callback APIs and FastAPI entry points, by defining qualified name. A test checks every name
159
+ exists in a real environment.
160
+
161
+ Sync `def` endpoints and dependencies are not reported: FastAPI runs them in a threadpool.
162
+
163
+ ## Limits
164
+
165
+ Python is dynamic: calls through `getattr`, untyped parameters, or containers of callables cannot
166
+ always be resolved. They are never silently dropped — `--strict` lists every one reachable on the
167
+ loop. Not modeled yet: `getattr(obj, "name")`, containers of callables (`HANDLERS[kind]()`),
168
+ metaclasses, and `.pyi`-only libraries under `--follow-libs`. A change to any source file re-extracts the whole
169
+ project (no per-file incremental cache yet).
170
+
171
+ ## Development
172
+
173
+ ```sh
174
+ cargo test --workspace # needs `uv`; syncs tests/fixtures/.venv from uv.lock
175
+ scripts/corpus.sh [names] # Polar, mealie, lnbits, dispatch, litellm, open-webui, template
176
+ ```
177
+
178
+ Fixture cases live in `tests/fixtures/cases/*`; expectations are inline comments:
179
+ `# expect: time.sleep via route GET /x`, `# expect: *` (any blocking function), `# expect-unresolved`.
180
+
181
+ `tests/adversarial/` is a suite of cases written to break hydroid, split into a training half
182
+ (required to pass, by `cargo test`) and a held-out half that is only scored:
183
+
184
+ ```sh
185
+ scripts/hillclimb/score.py --split train # misses and false positives, per case
186
+ scripts/hillclimb/score.py --split holdout --quiet # aggregate only: don't tune on it
187
+ scripts/hillclimb/polar_time.sh target/release/hydroid 5
188
+ uv run --with anthropic scripts/hillclimb/adversary.py --count 5 # Claude proposes new cases
189
+ scripts/hillclimb/oracle.py PROJECT report.json # naive syntactic scan: candidate misses
190
+ ```
191
+
192
+ ty's crates are pinned to exact versions (`=0.0.15`); they have no API stability promise.
193
+ The bundled typeshed stubs are Apache-2.0; hydroid is MIT.
194
+
195
+ Releases: [docs/RELEASING.md](docs/RELEASING.md).
196
+
@@ -0,0 +1,7 @@
1
+ hydroid_cli-0.2.0.data/scripts/hydroid.exe,sha256=4zWisHaIOMcTinjywCY_t7c7r7xU5gZbFBLQ43Wh1OM,30542848
2
+ hydroid_cli-0.2.0.dist-info/METADATA,sha256=gzFptqa49IPP91I4uGYhEQV9w9qF7QaaQvI5gB6SpAk,9591
3
+ hydroid_cli-0.2.0.dist-info/WHEEL,sha256=8Aej0W0a6Cz6apA3IzJrTnxLRVLAt-w0Oh8SA3Con_c,94
4
+ hydroid_cli-0.2.0.dist-info/licenses/LICENSE,sha256=upidXZ4y-vdM080kmWMiVUyX8Hf6lmA6qtCxwHkz6JA,1086
5
+ hydroid_cli-0.2.0.dist-info/licenses/THIRD_PARTY_NOTICES.md,sha256=2a_aC8UPqlMrD97IyGyeqVVc1BO2CJuvwzh7qI2S9KU,618
6
+ hydroid_cli-0.2.0.dist-info/sboms/hydroid.cyclonedx.json,sha256=GydtpAND6tKmKcHNfTif8BoZ56nVpZOId826BBTd3X8,261427
7
+ hydroid_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.15.0)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agusmdev
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.
@@ -0,0 +1,11 @@
1
+ # Third-party notices
2
+
3
+ hydroid links the following and ships them inside its binary:
4
+
5
+ - **ruff / ty crates** (`ruff_db`, `ruff_python_ast`, `ty_python_semantic`, …) — MIT,
6
+ Copyright (c) 2022 Charles Marsh. <https://github.com/astral-sh/ruff>
7
+ - **typeshed** stub files, bundled by `ty_vendored` — Apache License 2.0,
8
+ Copyright the typeshed contributors. <https://github.com/python/typeshed>
9
+ Full license text: <https://github.com/python/typeshed/blob/main/LICENSE>
10
+ - Other Rust dependencies are listed in `Cargo.lock`; their licenses (MIT and/or Apache-2.0)
11
+ can be listed with `cargo license`.