Problem
Nothing in the emitted schema says which callables a framework invokes from outside the
application's own call graph. A call graph without identified roots cannot answer reachability,
dead-code, or attack-surface questions, and every consumer re-derives entrypoints ad hoc.
The gap is load-bearing downstream. Java emits is_entrypoint and is_entrypoint_class
(python-sdk/cldk/models/java/models.py:343,403), so JavaAnalysis.get_entry_point_* are real
methods. The Python facade mirrors the same surface but all four
— get_entry_point_classes, get_entry_point_methods, get_service_entry_point_classes,
get_service_entry_point_methods — raise NotImplementedError
(python-sdk/cldk/analysis/python/python_analysis.py:881-960), purely because the backend
supplies no data.
Scope boundary
Entrypoints only. CRUD detection is specced separately. is_entrypoint_class covers
inheritance-based entrypoints only, not a coarse "worth analyzing" filter as in Java — in Python
the function-level predicate does the real work.
Goals
| Finder |
Signals |
flask |
@app.route, @bp.get|post|…, MethodView/View bases |
fastapi |
@app|router.get|post|websocket, router mounts via ModuleContext |
django |
CBV bases, DRF @api_view/@action, urls.py resolution |
tornado |
RequestHandler subclass |
celery |
@app.task, @shared_task, @periodic_task |
aws_lambda |
handler(event, context) + SAM/serverless binding |
cli |
@click.command, @app.command |
grpc |
*Servicer subclass |
Routing resolvers: django_url_resolver (path/re_path/url/include chains, .as_view()),
fastapi_router_resolver (include_router(prefix=…), mount), flask_blueprint_resolver
(Blueprint + register_blueprint).
Caveats and known risks
- The routing pre-pass is the only piece with no Java analog and carries most of the risk.
- Decorator-only detection was rejected: it misses Django entirely, where views bind in
urls.py.
- Literal JackEE (Datalog/Doop fact ingestion) was rejected: this analyzer is AST-based, and Java already ported the same architecture to ASTs.
- Resolving in the SDK was rejected: it sees only the serialized schema and cannot re-run AST passes.
- Tree-sitter suffices for decorators and base classes; Jedi is needed only for cross-module base resolution.
Definition of done
- A Flask
@app.route view, a FastAPI @router.get handler, a Celery @shared_task, and a Click @cli.command each carry is_entrypoint=True and the right entrypoint_framework
- A Django function view with no decorator, bound only via
path('...', view) through one include(), is flagged — the routing pre-pass gate
- An internally-called helper is not flagged
- Pre-existing
analysis.json files still load
PythonAnalysis.get_entry_point_* become thin readers over these fields, matching JavaAnalysis
Reference
JackEE — Antoniadis et al., "Static Analysis of Java Enterprise Applications", PLDI 2020.
Java mirror: src/main/java/com/ibm/cldk/javaee/.
Problem
Nothing in the emitted schema says which callables a framework invokes from outside the
application's own call graph. A call graph without identified roots cannot answer reachability,
dead-code, or attack-surface questions, and every consumer re-derives entrypoints ad hoc.
The gap is load-bearing downstream. Java emits
is_entrypointandis_entrypoint_class(
python-sdk/cldk/models/java/models.py:343,403), soJavaAnalysis.get_entry_point_*are realmethods. The Python facade mirrors the same surface but all four
—
get_entry_point_classes,get_entry_point_methods,get_service_entry_point_classes,get_service_entry_point_methods— raiseNotImplementedError(
python-sdk/cldk/analysis/python/python_analysis.py:881-960), purely because the backendsupplies no data.
Scope boundary
Entrypoints only. CRUD detection is specced separately.
is_entrypoint_classcoversinheritance-based entrypoints only, not a coarse "worth analyzing" filter as in Java — in Python
the function-level predicate does the real work.
Goals
PyClass.is_entrypoint,PyClass.entrypoint_framework,PyCallable.is_entrypoint,PyCallable.entrypoint_frameworkframeworks/_base.py:AbstractEntrypointFinderplusModuleContextcarrying per-project routing factsentrypoint_factory.py, OR-ing results (mirrors Java'sEntrypointsFinderFactory)frameworks/routing/, one pass per project, emitting{qualified_name → route_metadata}symbol_table_builder.py: routing pre-pass first, then set the fields during class/function constructionflask@app.route,@bp.get|post|…,MethodView/Viewbasesfastapi@app|router.get|post|websocket, router mounts viaModuleContextdjango@api_view/@action,urls.pyresolutiontornadoRequestHandlersubclasscelery@app.task,@shared_task,@periodic_taskaws_lambdahandler(event, context)+ SAM/serverless bindingcli@click.command,@app.commandgrpc*ServicersubclassRouting resolvers:
django_url_resolver(path/re_path/url/includechains,.as_view()),fastapi_router_resolver(include_router(prefix=…),mount),flask_blueprint_resolver(
Blueprint+register_blueprint).Caveats and known risks
urls.py.Definition of done
@app.routeview, a FastAPI@router.gethandler, a Celery@shared_task, and a Click@cli.commandeach carryis_entrypoint=Trueand the rightentrypoint_frameworkpath('...', view)through oneinclude(), is flagged — the routing pre-pass gateanalysis.jsonfiles still loadPythonAnalysis.get_entry_point_*become thin readers over these fields, matchingJavaAnalysisReference
JackEE — Antoniadis et al., "Static Analysis of Java Enterprise Applications", PLDI 2020.
Java mirror:
src/main/java/com/ibm/cldk/javaee/.