Structural Python search and lightweight AST-based rule checking
Project description
pyastq
pyastq is a structural Python searcher and lightweight rule runner. It searches
the Python AST rather than raw text, making it suitable for local scripts,
pre-commit hooks, and CI checks.
Build
cargo build --release
The binary is written to target/release/pyastq.
Install
pyastq is a Rust executable distributed as the pyastq Python package. Install
it as an isolated command-line tool:
uv tool install pyastq
# or
pipx install pyastq
For local development:
uv tool install .
pyastq --help
The PyPI package is only a distribution mechanism for the executable; it does not provide an importable Python module.
Versions
Release tags are the source of truth for published package versions. To publish a new release:
- Tag the commit, for example
git tag v0.2.0. - Push the tag with
git push origin v0.2.0.
The release workflow validates the tag, changes the package version in its
temporary checkout, builds wheels for Linux, macOS, and Windows, publishes them
to PyPI, and creates a GitHub release. Cargo.toml may therefore still show the
development version from the tagged commit.
An existing tag can be released from GitHub Actions by running the Release
workflow manually and entering the tag.
PyPI retains previously published versions, so users can select one explicitly:
uv tool install 'pyastq==0.1.0'
pipx install 'pyastq==0.1.0'
Uploading another build with the same version is not a replacement mechanism.
Fixes require a new version such as 0.1.1.
Structural Search
pyastq find src 'call:eval'
pyastq find src 'class:* -> function:regex:^[A-Z]'
pyastq find src 'call:request AND argument:timeout:>30'
pyastq find src 'function:* AND descendant(call:open) AND NOT descendant(call:close)'
pyastq find src 'call:print AND ancestor(function:*)'
The first pattern is the node reported as the finding. Conditions inspect its structure:
patternanddescendant(pattern)search all nested nodes.child(pattern)searches direct AST children.ancestor(pattern)andinside(pattern)search enclosing nodes.->is shorthand forAND descendant.AND,OR,NOT, and parentheses compose conditions.
Supported node patterns:
call:<value>
class:<value>
function:<value>
import:<value>
argument:<key>:<value>
Argument keys are keyword names, zero-based positional indexes, or *:
argument:timeout:30
argument:0:"input.txt"
argument:*:None
Value predicates:
* any value
User exact value
exact:User exact value
contains:User substring
starts_with:test_ prefix
ends_with:_unsafe suffix
regex:^[A-Z] regular expression
>3 >=3 <10 <=10 numeric comparison
Quote a complete query when invoking it from a shell. Quotes inside a value
allow spaces, for example argument:0:"hello world".
Automation
find returns 0 unless parsing or execution fails. Use --fail-on-match to
make matches return 1:
pyastq find . 'call:eval' --fail-on-match --quiet
Exit codes:
0: successful and clean1: findings were detected when failure-on-match applies2: invalid query, configuration, or execution error
Output and filtering options:
pyastq find . 'call:eval' --format json
pyastq find . 'call:eval' --format jsonl
pyastq find . 'call:eval' --format sarif
pyastq find . 'call:eval' --include 'src/**/*.py' --exclude '**/generated/**'
pyastq find . 'call:eval' --changed --max-matches 10
pyastq find . 'call:eval' --no-cache
--changed includes staged, unstaged, and untracked Python files reported by
Git.
Directory searches store one content hash per file and findings per query or
rule in .pyastq-cache.json. Unchanged files reuse cached findings. Changed files
are read, hashed, and parsed once, then all applicable rules run against the
same syntax tree. Use --no-cache to force a full scan. Cache failures fall
back to a full scan, and --changed or --max-matches searches do not use the
cache.
Rule Files
Rules use TOML. See pyastq.example.toml.
exclude = ["**/generated/**"]
[[rules]]
id = "no-eval"
query = "call:eval"
message = "Avoid eval(); parse the expected input explicitly."
severity = "error"
include = ["src/**/*.py"]
valid = ["parse(value)"]
invalid = ["eval(value)"]
[[rules]]
id = "method-name-case"
query = "class:* -> function:regex:^[A-Z]"
message = "Method names must start with a lowercase letter."
severity = "warning"
Run rules:
pyastq check . --rules pyastq.toml
pyastq check . --rules pyastq.toml --format json --changed
pyastq test-rules --rules pyastq.toml
check returns 1 when any rule matches. test-rules verifies that each
valid example does not match and each invalid example does.
Rules can also live in pyproject.toml:
[tool.pyastq]
exclude = ["generated/**", "migrations/**"]
[[tool.pyastq.rules]]
id = "no-eval"
query = "call:eval"
message = "Avoid eval(); parse the expected input explicitly."
severity = "error"
valid = ["parse(value)"]
invalid = ["eval(value)"]
Alternatively, reference a standalone rule file:
[tool.pyastq]
rules-file = "config/pyastq.toml"
exclude = ["build/**"]
rules-file is resolved relative to pyproject.toml. External and inline
rules may be used together: external rules are loaded first, inline rules are
appended, and exclusions from both configurations are combined. Rule IDs must
remain unique across both sources.
When --rules is omitted, check searches from the analyzed path toward the
filesystem root for a pyproject.toml containing [tool.pyastq]:
pyastq check .
pyastq test-rules
Passing --rules continues to support standalone rule files and explicit
pyproject.toml files:
pyastq check . --rules pyastq.toml
pyastq check . --rules pyproject.toml
SARIF 2.1.0 output is suitable for code-scanning systems:
pyastq check . --format sarif > pyastq.sarif
Suppressions
Suppress one line, the following line, or an entire file:
eval(value) # pyastq: ignore no-eval
# pyastq: ignore no-eval
eval(value)
# pyastq: ignore-file no-eval
Omitting the rule ID suppresses every rule at that location. Multiple IDs can be separated by spaces or commas.
Pre-commit Example
repos:
- repo: local
hooks:
- id: pyastq
name: pyastq structural rules
entry: target/release/pyastq check . --rules pyastq.toml --changed
language: system
pass_filenames: false
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyastq-0.1.2-py3-none-win_amd64.whl.
File metadata
- Download URL: pyastq-0.1.2-py3-none-win_amd64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f04d054f206bd1686282645c42a29b9803fd6e78e1a13922d9be90db10fac44
|
|
| MD5 |
7be77b28f953143999808858d564c9bc
|
|
| BLAKE2b-256 |
ed1525d42c0d32d97417fbce75fa6e6cca479484acbad35b9e78d79c8d21cc90
|
Provenance
The following attestation bundles were made for pyastq-0.1.2-py3-none-win_amd64.whl:
Publisher:
release.yml on PetarMishov/pyastq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyastq-0.1.2-py3-none-win_amd64.whl -
Subject digest:
4f04d054f206bd1686282645c42a29b9803fd6e78e1a13922d9be90db10fac44 - Sigstore transparency entry: 1817145105
- Sigstore integration time:
-
Permalink:
PetarMishov/pyastq@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/PetarMishov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyastq-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pyastq-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2525a75eec329bcff09a519a13e556692d1b7d9e672f6b2282b516f90b80f398
|
|
| MD5 |
48cd3a2f0ecdbbb450fb6d77a925cc2a
|
|
| BLAKE2b-256 |
57c1abe9aa567b1de0779bc6cde86338c498c1763510507fda2414c448e5f73a
|
Provenance
The following attestation bundles were made for pyastq-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on PetarMishov/pyastq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyastq-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
2525a75eec329bcff09a519a13e556692d1b7d9e672f6b2282b516f90b80f398 - Sigstore transparency entry: 1817145793
- Sigstore integration time:
-
Permalink:
PetarMishov/pyastq@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/PetarMishov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyastq-0.1.2-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: pyastq-0.1.2-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.3 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6600e221271278aeba58f2dd95f7c7a5fd2c20f735e0cde20c824e0011fd3e92
|
|
| MD5 |
cf7709c7b5fc5069d94c5686e82064cf
|
|
| BLAKE2b-256 |
a5d937c5045b176d258b4cec13aea0097c6cbddc0242832153e8b9810d8f9b61
|
Provenance
The following attestation bundles were made for pyastq-0.1.2-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on PetarMishov/pyastq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyastq-0.1.2-py3-none-macosx_11_0_arm64.whl -
Subject digest:
6600e221271278aeba58f2dd95f7c7a5fd2c20f735e0cde20c824e0011fd3e92 - Sigstore transparency entry: 1817145880
- Sigstore integration time:
-
Permalink:
PetarMishov/pyastq@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/PetarMishov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyastq-0.1.2-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: pyastq-0.1.2-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed61b3cc8c8fb2d91974863ad31c607e0503d0c80b73ccf02e037d1f7ce7fd7f
|
|
| MD5 |
45a4cb0e25f957c361bbc051ecb4ca3c
|
|
| BLAKE2b-256 |
049d3a3b0522c5d77ff78c9929ffe11b5ba2b587dc24ff6a44be5b2a6590ed7b
|
Provenance
The following attestation bundles were made for pyastq-0.1.2-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on PetarMishov/pyastq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyastq-0.1.2-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
ed61b3cc8c8fb2d91974863ad31c607e0503d0c80b73ccf02e037d1f7ce7fd7f - Sigstore transparency entry: 1817145516
- Sigstore integration time:
-
Permalink:
PetarMishov/pyastq@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/PetarMishov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@afa98ff79006dec39ebdb844ffc557b0b0ea59ea -
Trigger Event:
push
-
Statement type: