Static analysis for Python — canonical schema v2 (symbol table, call graph, and native CFG/PDG/SDG dataflow) as analysis.json or a Neo4j property graph.
Project description
codeanalyzer-python (canpy)
A Python static-analysis toolkit — the CLDK backend that emits the canonical schema v2 Code Property Graph, as analysis.json or a Neo4j property graph.
canpy is a static analyzer for Python built on Jedi,
PyCG, and Tree-sitter. It
emits the canonical CodeLLM-DevKit (CLDK) schema v2 — a single, additive Code Property Graph
tree — either as analysis.json or projected into a Neo4j property graph. It is the Python
backend behind CLDK, mirroring its
TypeScript (cants) and
Java siblings.
The payload is one tree grown one layer at a time across four analysis levels (-a 1|2|3|4): a
symbol table, a call graph, intraprocedural control- and data-dependence graphs, and a whole-program
interprocedural system dependence graph. Each level is a strict superset of the one below it
(analysis.json(-a 1) ⊆ … ⊆ analysis.json(-a 4)), so a consumer can request exactly the depth it
needs.
Table of Contents
- Features
- Installation
- Usage
- Analysis levels
- Architecture & Tooling
- Output shape (canonical schema v2)
- Output targets
- Development
- License
Features
- Canonical schema v2 — one additive Code Property Graph tree (
schema_version2.0.0), stamped withlanguage,max_level,analyzer{name,version}, and (at L3+)k_limit, rooted at a singleapplicationnode with durablecan://ids on every callable and above. - Symbol table — modules, classes, functions, methods, variables, decorators, imports, and
docstrings, with precise byte-offset source spans; each module carries its
sourceonce. - Call graph — Jedi's lexical resolver at level 1, enriched with PyCG-resolved edges at level 2 (provenance-tagged, coupling-aware sharding for large apps).
- Dataflow graphs — native, per-callable exceptional CFG plus control- and
data-dependence edges (
cfg/cdg/ddg) at level 3, stitched into a whole-program interprocedural SDG (synthetic parameter vertices,param_in/param_out/summary, alias-aware DDG) at level 4 — all built in-process from the stdlibast. - Neo4j output — project the analysis into a labeled property graph: a self-contained
graph.cyphersnapshot, or an incremental push to a live database over Bolt. - Versioned schema — a machine-readable, version-stamped Neo4j schema contract (
--emit schema), checked in asschema.neo4j.json(2.0.0) and shipped with every release. - Incremental cache — per-file results are cached under
.codeanalyzer;--lazy(default) reuses them,--eagerforces a clean rebuild.--raydistributes the work across cores. - Compact output — canonical
analysis.json, or binaryanalysis.msgpackfor smaller artifacts.
Installation
Prerequisites
-
Python 3.10 or newer.
-
A C toolchain and the
venv/ development headers — the analyzer builds an isolated virtual environment per project (via Python'svenv) so Jedi can resolve types and imports:# Ubuntu / Debian sudo apt install python3-venv python3-dev build-essential # Fedora / RHEL / CentOS sudo dnf group install "Development Tools" && sudo dnf install python3-venv python3-devel # macOS xcode-select --install
Install via pip (PyPI)
pip install codeanalyzer-python
canpy --help
For the optional live Neo4j push (--emit neo4j --neo4j-uri …), install the neo4j extra:
pip install 'codeanalyzer-python[neo4j]'
For the Scalpel-backed points-to oracle at level 4, install the scalpel extra. It is optional:
when it is absent, level 4 automatically falls back to the built-in type-based oracle.
pip install 'codeanalyzer-python[scalpel]'
Install via shell script
Install the CLI as an isolated tool with the one-line installer (provisions via uv / pipx / pip):
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-python/releases/latest/download/canpy-installer.sh | sh
Install via Homebrew
brew install codellm-devkit/tap/codeanalyzer-python
The formula depends on uv and installs canpy as an isolated,
version-pinned uv tool (the package and its dependencies are resolved and cached on first run).
Build from source
This project uses uv for dependency management.
git clone https://github.com/codellm-devkit/codeanalyzer-python
cd codeanalyzer-python
uv sync --all-groups
uv run canpy --help
Usage
canpy --input /path/to/python/project
With no --output, the analysis is printed to stdout as compact JSON; with --output <dir> it is
written to analysis.json (or graph.cypher for --emit neo4j, or analysis.msgpack with
--format msgpack) in that directory.
Options
$ canpy --help
Usage: canpy [OPTIONS] COMMAND [ARGS]...
Static Analysis on Python source code using Jedi, PyCG and Tree sitter.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --version Show the canpy │
│ version and │
│ exit. │
│ --input -i <path> Path to the │
│ project root │
│ directory (not │
│ required for │
│ --emit schema). │
│ --output -o <path> Output directory │
│ for artifacts. │
│ --format -f <json|msgpack> Output format │
│ for --emit json: │
│ json or msgpack. │
│ [default: json] │
│ --emit <json|neo4j|sche Output target: │
│ ma> json │
│ (analysis.json, │
│ default) | neo4j │
│ (graph.cypher or │
│ live Bolt push) │
│ | schema (the │
│ Neo4j │
│ schema.json │
│ contract). │
│ [default: json] │
│ --app-name <str> Logical │
│ application name │
│ for the graph │
│ :PyApplication │
│ anchor (default: │
│ input dir name). │
│ --neo4j-uri <str> Push the graph │
│ to a live Neo4j │
│ over Bolt │
│ (incremental); │
│ omit to write │
│ graph.cypher. │
│ [env var: │
│ NEO4J_URI] │
│ --neo4j-user <str> Neo4j username. │
│ [env var: │
│ NEO4J_USERNAME] │
│ [default: neo4j] │
│ --neo4j-password <str> Neo4j password. │
│ Prefer the env │
│ var over the │
│ flag (the flag │
│ is visible in │
│ shell history / │
│ process list). │
│ [env var: │
│ NEO4J_PASSWORD] │
│ [default: neo4j] │
│ --neo4j-database <str> Neo4j database │
│ name (default: │
│ server default). │
│ [env var: │
│ NEO4J_DATABASE] │
│ --analysis-level -a <int range> Analysis depth: │
│ [1<=x<=4] 1=symbol │
│ table+Jedi call │
│ graph, 2=+PyCG │
│ call graph, │
│ 3=+native │
│ intraprocedural │
│ dataflow │
│ (CFG/PDG), │
│ 4=+interprocedu… │
│ SDG │
│ (param/summary │
│ edges, │
│ alias-aware │
│ DDG). │
│ [default: 1] │
│ --graphs <str> Level 3+ only: │
│ comma-separated │
│ program-graph │
│ sections to emit │
│ (cfg, dfg, pdg, │
│ sdg). Default: │
│ cfg,dfg,pdg. │
│ `dfg` emits the │
│ PDG's data edges │
│ only; `sdg` │
│ requires -a 4. │
│ [default: │
│ cfg,dfg,pdg] │
│ --graph-field-de… <int range> Level 3 only: │
│ [x>=1] k-limit on │
│ access-path │
│ depth (x.f.g.h │
│ with k=3 becomes │
│ x.f.g.*). │
│ Mandatory bound │
│ — it is what │
│ guarantees the │
│ interprocedural │
│ fixpoint │
│ terminates. │
│ [default: 3] │
│ --ray --no-ray Enable Ray for │
│ distributed │
│ analysis. │
│ [default: │
│ no-ray] │
│ --eager --lazy Enable eager or │
│ lazy analysis. │
│ Defaults to │
│ lazy. │
│ [default: lazy] │
│ --skip-tests --include-tests Skip test files │
│ in analysis. │
│ [default: │
│ skip-tests] │
│ --no-venv --venv Skip virtualenv │
│ creation and │
│ dependency │
│ installation; │
│ resolve imports │
│ against the │
│ ambient Python │
│ environment │
│ instead. │
│ [default: venv] │
│ --file-name <path> Analyze only the │
│ specified file │
│ (relative to │
│ input │
│ directory). │
│ --cache-dir -c <path> Directory to │
│ store analysis │
│ cache. Defaults │
│ to │
│ '.codeanalyzer' │
│ in the input │
│ directory. │
│ --clear-cache --keep-cache Clear cache │
│ after analysis. │
│ By default, │
│ cache is │
│ retained. │
│ [default: │
│ keep-cache] │
│ -v <int> Increase │
│ verbosity: -v, │
│ -vv, -vvv │
│ [default: 0] │
│ --pycg-shard --no-pycg-shard Shard PyCG │
│ call-graph │
│ analysis by │
│ Python package │
│ (level 2 only). │
│ When the project │
│ exceeds the │
│ 500-file │
│ ceiling, PyCG is │
│ run │
│ independently │
│ per top-level │
│ package with │
│ cross-package │
│ imports treated │
│ as ghost nodes. │
│ Without this │
│ flag, projects │
│ over the ceiling │
│ fall back to │
│ Jedi-only edges. │
│ [default: │
│ no-pycg-shard] │
│ --pycg-shard-cei… <int range> Maximum files │
│ [x>=1] per shard when │
│ --pycg-shard is │
│ active (default │
│ 100). Shards │
│ exceeding this │
│ limit are │
│ skipped; their │
│ call edges are │
│ omitted from the │
│ call graph (Jedi │
│ edges for those │
│ packages are │
│ still included). │
│ Lower values are │
│ safer for │
│ packages with │
│ deep class │
│ hierarchies or │
│ heavy import │
│ graphs. │
│ [default: 100] │
│ --pycg-shard-tim… <int range> Per-shard │
│ [x>=0] wall-clock │
│ timeout in │
│ seconds when │
│ --pycg-shard is │
│ active (default │
│ 120). A shard │
│ that exceeds │
│ this limit is │
│ skipped │
│ gracefully. │
│ PyCG's fixpoint │
│ is bimodal: it │
│ either converges │
│ quickly or │
│ diverges │
│ indefinitely, so │
│ the timeout acts │
│ as a final │
│ safety net after │
│ the file-count │
│ ceiling. Set to │
│ 0 to disable. │
│ POSIX only │
│ (macOS / Linux); │
│ ignored on │
│ Windows. │
│ [default: 120] │
│ --pycg-shard-str… <jedi|package> How --pycg-shard │
│ groups files │
│ (level 2 only). │
│ 'jedi' (default) │
│ partitions the │
│ Jedi │
│ module-dependen… │
│ graph (SCC + │
│ Louvain) so │
│ tightly-coupled │
│ modules │
│ co-compute and │
│ few call edges │
│ are severed │
│ between shards; │
│ import cycles │
│ are never split. │
│ 'package' uses │
│ the legacy │
│ one-shard-per-p… │
│ grouping. │
│ [default: jedi] │
│ --pycg-max-iter <int range> Cap on PyCG's │
│ [x>=-1] fixpoint passes │
│ per │
│ shard/project │
│ (level 2; │
│ default 50). │
│ PyCG iterates │
│ until its │
│ points-to state │
│ stops changing, │
│ but its │
│ access-path │
│ domain has no │
│ convergence │
│ bound, so heavy │
│ metaclass/mixin │
│ code (e.g. an │
│ ORM) can loop │
│ with each pass │
│ costing seconds. │
│ The cap returns │
│ a │
│ sound-but-incom… │
│ call graph │
│ instead of │
│ looping until │
│ the timeout │
│ kills it. Set to │
│ -1 for PyCG's │
│ unbounded │
│ run-to-converge… │
│ behaviour. │
│ [default: 50] │
│ --help Show this │
│ message and │
│ exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
Examples
-
Basic analysis to stdout, or to a file:
canpy --input ./my-python-project # compact JSON on stdout canpy --input ./my-python-project --output ./out # → ./out/analysis.json
-
Binary output (msgpack):
canpy --input ./my-python-project --output ./out --format msgpack # → ./out/analysis.msgpack
-
Enrich the call graph with PyCG (level 2):
canpy --input ./my-python-project -a 2
Level 1 edges come from Jedi's lexical resolution.
-a 2runs PyCG and merges its flow-sensitive edges in (RPC / third-party / dynamically-dispatched targets), backfilling callees Jedi could not resolve. Every edge is provenance-tagged (e.g.jedi,pycg). -
Emit a Neo4j snapshot, or push to a live database:
canpy --input ./my-python-project --emit neo4j --output ./out # → ./out/graph.cypher canpy --input ./my-python-project --emit neo4j \ --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password secret
-
Emit the Neo4j schema contract:
canpy --emit schema # print schema.json to stdout (no project needed) canpy --emit schema --output ./out # → ./out/schema.json
-
Force a clean rebuild with a custom cache directory:
canpy --input ./my-python-project --eager --cache-dir /path/to/custom-cache
-
Dataflow graphs — intraprocedural (level 3) and interprocedural (level 4):
canpy --input ./my-python-project -a 3 --output ./out # per-callable cfg/cdg/ddg canpy --input ./my-python-project -a 4 --output ./out # + interprocedural SDG canpy --input ./my-python-project -a 3 --graphs cfg,pdg # scope the emitted sections canpy --input ./my-python-project -a 4 --graphs sdg # sdg requires -a 4 canpy --input ./my-python-project -a 3 --graph-field-depth 2 # tighter access-path k-limit
Levels 3 and 4 also enrich the Neo4j projection (
--emit neo4j) with the CPG overlay (:PyCFGNodenodes wired byPY_CFG_NEXT/PY_CDG/PY_DDG, plus the level-4PY_PARAM_IN/PY_PARAM_OUT/PY_SUMMARYedges — the cross-language dataflow vocabulary, PY_-namespaced like every other row family so multi-language databases never mingle analyzers' edges).
Analysis levels
Each level is the same tree grown one layer deeper, plus the edge family over that new layer. The
levels are cumulative and additive — analysis.json(-a 1) ⊆ … ⊆ analysis.json(-a 4).
| Level | Flag | What it adds | Where it lands |
|---|---|---|---|
| 1 | -a 1 (default) |
Symbol table, Jedi call graph, and call nodes in each callable's body |
body calls (callee: null) |
| 2 | -a 2 |
PyCG call-graph enrichment; each call's callee backfilled to a can:// id |
call_graph, body callees |
| 3 | -a 3 |
Native intraprocedural CFG/CDG/DDG (syntactic, name-equality, prov: ["ssa"]) |
cfg, cdg, ddg, @entry/@exit on each callable |
| 4 | -a 4 |
Interprocedural SDG: synthetic param vertices, alias-aware DDG (prov: ["points-to"]) |
param_in, param_out, summary, semantic ddg |
-a 1/-a 2 timings and output are unaffected by the heavier levels — nothing at level 3+ runs
unless requested. Flag gating: --graphs sdg requires -a 4; --graphs cfg,dfg,pdg and
--graph-field-depth require -a 3.
Architecture & Tooling
The dataflow substrate is hand-built from the standard library so every graph node joins back to a symbol-table signature by construction (#67):
- CFG source: a hand-built exceptional control-flow graph from the stdlib
astmodule — the same parse the symbol-table builder uses. One synthetic@entry/@exitper callable, statement-level nodes keyedline:colin source order, with exception /yield/awaitedges first-class. - Def-use source: hand-built reaching definitions (a classic forward worklist) over
k-limited access paths (
--graph-field-depth, default 3) — there is no usable SSA library for Python. This yields the level-3 syntactic DDG (name-equality,prov: ["ssa"]). - Points-to oracle (level 4): the Scalpel may-alias oracle —
ScalpelAliasOracle(codeanalyzer/dataflow/scalpel_oracle.py) — consumes Scalpel's SSA copy/const facts to answermay_alias(path_a, path_b), adding the alias-aware DDG edges (prov: ["points-to"]) and the interprocedural summaries.python-scalpelis an optional dependency (pip install 'codeanalyzer-python[scalpel]'); when it is absent or cannot resolve a construct, the analyzer automatically falls back to the built-inTypeBasedAliasOracle(Jedi-inferred types; unknown types conservatively alias), keeping themay_aliasinterface total. Call dispatch comes from the merged Jedi(+PyCG) call graph, treated as a frozen oracle. - Summaries: relational formal-in → formal-out flows composed bottom-up over the Tarjan SCC condensation of the call graph, a monotone fixpoint within SCCs; globals ride as extra formals, closure captures bind at definition sites.
- Slicing and taint are the SDK's responsibility. A backward slicer ships in-process
(
codeanalyzer.dataflow.slicing), but only as an internal validation utility for the L3/L4 gates — it is not a product surface. Once the SDG is emitted, slicing and taint become language-independent labeled reachability and belong to the CLDK SDK across the provider/client boundary; the analyzer emits thesummarysubstrate and notaint_flowssection. - Precision posture: sound-leaning and over-approximate — prefer false positives to missed
flows. Known unsoundness (documented, not silently absorbed):
eval/exec, reflection (getattr/setattrwith dynamic names), monkey-patching, C extensions,importside effects, and module top-level statements (globals are modeled as formals instead).
Output shape (canonical schema v2)
Every run produces the same envelope — an Analysis document — regardless of level; deeper levels
just populate more of the same tree:
{
"schema_version": "2.0.0",
"language": "python",
"max_level": 4, // the level this run was produced at
"k_limit": 3, // access-path depth bound (--graph-field-depth); L3+ only
"analyzer": { "name": "codeanalyzer-python", "version": "1.0.0" },
"application": {
"id": "can://python/<app>",
"kind": "application",
"symbol_table": { // relative POSIX path → module
"pkg/mod.py": {
"id": "can://python/<app>/pkg/mod.py",
"kind": "module",
"source": "…full file text, stored once per module…",
"types": { "<Class>": { "id": "…", "kind": "class", "callables": { /* methods */ } } },
"functions": { "<sig>": { /* callable, see below */ } }
}
},
"call_graph": [ { "src": "can://…/main(a)", "dst": "can://…/helper(x)",
"weight": 1, "prov": ["jedi", "pycg"] } ],
"external_symbols": { // imported/builtin call targets, keyed by id
"can://python/<app>/@external/os/getcwd":
{ "id": "can://python/<app>/@external/os/getcwd", "kind": "external",
"name": "getcwd", "module": "os" }
},
"param_in": [ { "src": "can://…/main(a)@6:4/actual_in:0", "dst": "can://…/helper(x)@formal_in:0" } ],
"param_out": [ { "src": "can://…/helper(x)@formal_out", "dst": "can://…/main(a)@6:4/actual_out" } ]
}
}
A callable (function or method) carries its own CPG, keyed by node id:
{
"id": "can://…/main(a)", "kind": "function",
"span": { "start": [5, 0], "end": [7, 12], "bytes": [43, 86] }, // byte offsets into module.source
"body": { // node id → node
"@entry": { "kind": "entry" },
"6:4": { "kind": "statement", "span": { … } },
"6:8": { "kind": "call", "span": { … }, "callee": "can://…/helper(x)" }, // callee null until L2
"@formal_in:0": { "kind": "formal_in", "of": "a" }, // L4 param vertices
"6:4/actual_in:0": { "kind": "actual_in", "of": "a", "parent": "6:4" },
"@exit": { "kind": "exit" }
},
"cfg": [ { "src": "@entry", "dst": "6:4", "kind": "fallthrough" } ], // L3
"cdg": [ { "src": "@entry", "dst": "6:4" } ], // L3
"ddg": [ { "src": "6:4", "dst": "7:4", "var": "h", "prov": ["ssa"] } ], // L3 ssa / L4 points-to
"summary": [ { "src": "6:4/actual_in:0", "dst": "6:4/actual_out" } ] // L4
}
Notable properties:
- Durable
can://ids identify every node at callable granularity and above (can://python/<app>/<file>/<callable-sig>); nodes below a callable use ordinal ids (@entry,@exit,line:col,@formal_in:N,line:col/actual_in:N). sourcelives once per module; every node's text is themodule.source[span.bytes]slice.- Cross-function edges —
call_graph,param_in,param_out— live at application scope; the intraproceduralcfg/cdg/ddgand thesummaryedges live on the callable. - No dangling endpoints — every
call_graphsrc/dstjoins the id space: declared callables by their tree id, imported/builtin targets by a…/@external/<module>/<name>id homed inapplication.external_symbols. - Breaking change from v1: there is no more flat top-level
symbol_table/call_graph, and no separate program-graphs section. Everything now hangs offapplication, and the dataflow graphs are inlined on each callable. Readanalysis.application.symbol_table(wasanalysis.symbol_table) andanalysis.application.call_graph(wasanalysis.call_graph).
Output targets
canpy builds one analysis in memory and can emit it three ways (--emit):
analysis.json (default)
The Analysis envelope described above. By default it is printed to stdout as JSON; with --output
it is written to analysis.json (or analysis.msgpack with --format msgpack, a more compact
binary format).
Neo4j graph
--emit neo4j projects the same schema v2.0.0 analysis into a labeled property graph. Every node
label is Py-prefixed and every relationship type is PY_-prefixed (e.g. :PyClass, PY_CALLS)
so multiple language analyzers can share one database without label or relationship-type collisions.
Declarations are keyed by their can:// id under a shared :PySymbol label; calls, imports,
inheritance, decorators, and call sites are relationships. At -a 3/-a 4 the projection gains the
CPG overlay — :PyCFGNode nodes (statements, and at level 4 the parameter vertices) wired by
PY_CFG_NEXT/PY_CDG/PY_DDG, plus the level-4 PY_PARAM_IN/PY_PARAM_OUT/PY_SUMMARY edges:
- Without
--neo4j-uri— writes a self-containedgraph.cypher(constraints + indexes, a scoped wipe, then batchedMERGEs). Load it withcypher-shell < graph.cypher. Needs no extra dependencies. - With
--neo4j-uri— pushes to a live Neo4j over Bolt incrementally: only modules whose content hash changed are rewritten, and on a full run modules whose source file vanished are pruned. Requires theneo4jextra. Every graph carries aschema_versionon its:PyApplicationnode.
Call-graph endpoints that aren't present in the symbol table (third-party / framework / RPC targets)
are materialized as :PyExternal ghost nodes, mirroring the analyzer's own ghost-node behaviour.
The connection options also read from the standard Neo4j environment variables — NEO4J_URI,
NEO4J_USERNAME, NEO4J_PASSWORD, NEO4J_DATABASE — when the corresponding flag is omitted (an
explicit flag wins). Prefer the env var for the password so it doesn't land in shell history or the
process list:
export NEO4J_URI=bolt://localhost:7687
export NEO4J_PASSWORD=secret
canpy -i ./my-project --emit neo4j # credentials picked up from the environment
Schema contract
--emit schema writes the machine-readable, version-stamped Neo4j schema (schema.json: node labels,
relationships, properties, constraints, and indexes; currently schema_version 2.0.0). It needs no
project and is checked into the repo as schema.neo4j.json and bundled in every release as a GitHub
Release asset, so a consumer can validate producer/consumer compatibility without invoking the tool.
The shape of the contract matches the
codeanalyzer-typescript backend.
A UML of the analysis.json schema (the PyApplication containment tree) is checked in as
schema-uml.drawio, and the property-graph schema as
neo4j-schema.drawio.
Development
This project uses uv.
uv sync --all-groups
uv run canpy --input /path/to/project # run from source
uv run canpy --emit schema > schema.neo4j.json # regenerate the checked-in schema contract
uv run python scripts/update_readme.py # regenerate the canpy --help block above
uv run pytest # run the test suite
The Neo4j schema-conformance test always runs. The Neo4j bolt integration test spins up a real Neo4j via Testcontainers and is opt-in — it needs a container runtime (Docker or Podman) and is enabled with an environment variable:
RUN_CONTAINER_TESTS=1 uv run pytest test/test_neo4j_bolt.py -s
License
Apache 2.0 — see LICENSE.
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 Distribution
Built Distribution
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 codeanalyzer_python-1.0.2.tar.gz.
File metadata
- Download URL: codeanalyzer_python-1.0.2.tar.gz
- Upload date:
- Size: 156.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53e85521c77f9acef96dc9189b07c88cf3343cc45f9bd709e3678c28ad76c07e
|
|
| MD5 |
ee261199edeb1085bdbddd68907a5dff
|
|
| BLAKE2b-256 |
6b0b82851b70b20d104d0dd332db64767dfac1025d9a9d2b5416362236c8c09a
|
File details
Details for the file codeanalyzer_python-1.0.2-py3-none-any.whl.
File metadata
- Download URL: codeanalyzer_python-1.0.2-py3-none-any.whl
- Upload date:
- Size: 155.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1772154817710259cde310a435ca51f7d22fe0f12cde04f4428883efa38dc49
|
|
| MD5 |
5b5d3768d95da92b95874cb1f3c166a8
|
|
| BLAKE2b-256 |
dfbbef502f9dd6106c9ad8f06a0bda977682fdf9c8549f7450b7af5384dabe07
|