Skip to content

Incorporate codeanalyzer-python v1.4.1 #325

Description

@rahlk

Is your feature request related to a problem? Please describe.

A new codeanalyzer-python release (v1.4.1) is published. Update the codeanalyzer-python pin in this repo and adapt the integration to the changes below (see PyCodeanalyzer._run_analyzer).

Describe the solution you'd like

Not stated in the original issue.

Describe alternatives you've considered

Not stated in the original issue.

Additional context

[1.4.1] - 2026-09-05

Added

  • A framework-independent heuristic tier for entrypoints. A decorator whose written spelling reads as an HTTP hook (route, *.route, *.*.route, or *.get/.post/.put/.patch/.delete/.head/.options/.websocket, one or two segments deep) is recorded with framework: "heuristic", confidence: "heuristic" and rule id heuristic.http-route / heuristic.http-verb, whether or not any framework was detected or any framework rule knows the library. Route and methods come from the arguments the same way as framework rules; a node a framework rule already matched gets no heuristic record. Shipped as a heuristics: block in rules.yml, disableable by id like every other rule. * now keeps its meaning inside a {a,b} alternation.
  • BodyNode.id and PyCallableParameter.id in analysis.json (TSNeo4jBackend.get_call_graph() omits synthesized anonymous-callback nodes — backend parity gap #176). A body node's id is the global ordinal <callable-id>@<local> the Neo4j projection already merges :PyBodyNode on, present at every level the node exists; a parameter's id is <callable-id>@formal_in:<i> for its position i, the level-4 formal_in vertex that carries it (a forward reference below -a 4). Both projections now name a body node the same way, so consumers stop recomposing the can:// grammar themselves. Additive; schema_version and the graph contract are unchanged.
  • The entrypoint report is projected to Neo4j (Cached analysis.json not invalidated when tsc_only (resolver mode) changes — stale call graph returned #177): :PyApplication carries entrypoint_frameworks (string[]) and entrypoint_report_json (the whole PyEntrypointReport), always present, so a graph consumer can tell "no entrypoints" from "the pass found nothing". Additive graph-catalog change.
  • odoo joins the shipped entrypoint rules: @http.route methods (route from the first positional, one route or a list; methods from methods=, default GET) and http.Controller subclasses.
  • Decorator matching falls back to the module's import table when Jedi cannot resolve the decorator (Cached analysis.json not invalidated when tsc_only (resolver mode) changes — stale call graph returned #177), the way base-class matching already did. So from odoo import http plus @http.route matches odoo.http.route with odoo not importable in the analysis environment, which is every --no-venv run.
  • :PyCanNode marker label on every node keyed by a can://python/ id, with a range index on id. It is the anchor the prefix predicates seek on; scope comes from the prefix, not the label. :PyExternal nodes carry it too, so external→application PY_CALLS edges sit inside the application scope (feat(typescript): add tsc_only toggle and surface synthesized anonymous callables #179).

Changed

  • The odoo entrypoint rule's default methods are [GET, POST], not [GET]: Odoo serves both on a route unless methods= narrows it, and json-typed routes are POST.
  • BREAKING (graph contract, version stays 2.0.0 — the v2 line has no released consumer): every destructive Neo4j statement is scoped on the can:// id prefix, and the internal _module property retires from every node, from the catalog and from its six per-label indexes (Reachability API: path witnesses between a source and a sink #173, epic Scope every destructive Neo4j statement on the can:// id prefix; retire _module .github#50). The per-module purge matches the module by id and its subtree by id STARTS WITH <module-id> + '/'; the full-run orphan prune and the snapshot wipe match can://python/<app>/. Two python applications sharing a module path no longer delete each other's nodes; an empty application id is refused instead of matching the whole store. Migration: a consumer that filtered on x._module filters on x.id STARTS WITH 'can://python/<app>/<file>/' instead.
  • BREAKING: :PyAttribute and :PyVariable ids are minted from the owner's can:// id — <class-id>/<name> and <owner-id>/<name>@<line> — instead of from its signature. The old ids (service.Service.name) carried no application segment, so two applications MERGEd onto one node.
  • A call target nobody homed now lands on an @external ghost under the application prefix (can://python/<app>/@external/<module>/<name>), never on a bare-signature id, so every id-keyed node is a can:// node.

Fixed

  • PY_EXTENDS is now actually emitted (Uptake codeanalyzer-python 0.3.0 (breaking: CodeQL removed, use analysis_level) #178). The projector handed the written base spelling (Base, views.View) to a lookup keyed by signature (pkg.mod.Base), so every inheritance edge was dropped as dangling and no graph since 2.0.0 carried one. Bases now resolve per module: a class declared in the module or reachable through its import table lands on that class's can:// id; anything else lands on an @external ghost with the same id shape call targets use. Relative imports resolve through resolved_module for entrypoint base matching too.
  • PyEntrypointReport.unresolved no longer counts what is nameable: Python builtins (object, Exception, str), subscripted generics (typing.Generic[T], dict[K, V]) and any spelling whose head is an imported name or a declared class. On Odoo it listed 24 Exception and 20 object bases as unresolved.
  • PyEntrypointReport.unresolved is now written (Cached analysis.json not invalidated when tsc_only (resolver mode) changes — stale call graph returned #177). It had no writer at all, so it read {} on every run; it now counts, per written spelling, the decorators and base classes that neither Jedi nor the import table could name.
  • The Bolt writer's content_hash diff compared the module row's can:// id against a file key and never matched, so every module counted as changed on every push. It now reads the module row directly, and keys the database side by module id under the application prefix: keyed by file key it was application-blind, so a second application whose module shared the path and the hash looked "unchanged" and was never written.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions