Python examples
These examples run against Flask, the sample project for the Python documentation. Each example calls one method of the CLDK Python SDK and shows the output of a real run over the Flask checkout. To use your own code, change project_path.
Set up the analysis
Section titled “Set up the analysis”Clone the Flask repository.
git clone https://github.com/pallets/flask.gitBuild a CLDK analysis object that points at the checkout. The analysis_level parameter controls the depth of the analysis. symbol_table, the default, finds modules, classes, and methods. call_graph adds the caller and callee edges. Every example on this page uses the call_graph level.
from cldk import CLDKfrom cldk.analysis import AnalysisLevel
analysis = CLDK.python( project_path="flask", analysis_level=AnalysisLevel.call_graph,)For the backend configuration, see codeanalyzer-python.
Symbol table: modules, classes, and methods
Section titled “Symbol table: modules, classes, and methods”The symbol table holds the modules, the classes, and the methods that the backend finds in the project.
List the modules
Section titled “List the modules”Each analyzed Python file becomes one module, keyed by its path from the root of the repository.
modules = analysis.get_modules()print(len(modules))# 35
symbol_table = analysis.get_symbol_table()for path in sorted(symbol_table)[:5]: print(path)# docs/conf.py# examples/celery/make_celery.py# examples/celery/src/task_app/__init__.py# examples/celery/src/task_app/tasks.py# examples/celery/src/task_app/views.pyList the classes
Section titled “List the classes”The key of each class is its dotted signature from the root of the repository.
classes = analysis.get_classes()print(len(classes))# 52
for name in sorted(classes)[:5]: print(name)# src.flask.app.Flask# src.flask.blueprints.Blueprint# src.flask.cli.AppGroup# src.flask.cli.CertParamType# src.flask.cli.FlaskGroupGet one class
Section titled “Get one class”Pass the dotted signature to get_class to get one class object.
pyclass = analysis.get_class("src.flask.app.Flask")print(len(pyclass.callables))# 35
print(sorted(pyclass.callables)[:5])# ['__call__', '__init__', '__init_subclass__', 'app_context', 'async_to_sync']List the methods of a class
Section titled “List the methods of a class”get_methods_in_class returns the methods that the class declares in its own body.
methods = analysis.get_methods_in_class("src.flask.blueprints.Blueprint")print(sorted(methods))# ['__init__', 'get_send_file_max_age', 'open_resource', 'send_static_file']Get one method
Section titled “Get one method”Pass the class signature and the method name to get_method.
method = analysis.get_method("src.flask.sansio.scaffold.Scaffold", "route")print(method.signature)print([parameter.name for parameter in method.parameters])# ['self', 'rule', 'options']Get the constructors
Section titled “Get the constructors”get_constructors returns the constructors of a class, keyed by name.
constructors = analysis.get_constructors("src.flask.app.Flask")print(list(constructors))# ['__init__']Filter the classes by name
Section titled “Filter the classes by name”get_classes_by_criteria matches the class signatures against your inclusion strings.
blueprint_classes = analysis.get_classes_by_criteria(inclusions=["Blueprint"])for name in sorted(blueprint_classes): print(name)# src.flask.sansio.blueprints.Blueprint# src.flask.sansio.blueprints.BlueprintSetupStateList the imports of a module
Section titled “List the imports of a module”get_imports returns the imports of every module, keyed by file path. Each import carries a name and a module.
imports = analysis.get_imports()wrappers = imports["src/flask/wrappers.py"]
for imported in wrappers[:4]: print(imported.name, "from", imported.module)# annotations from __future__# t from typing# BadRequest from werkzeug.exceptions# HTTPException from werkzeug.exceptionsCall graph: callers, callees, and reachability
Section titled “Call graph: callers, callees, and reachability”The call graph is a networkx.DiGraph. Its edges point from the caller to the callee. Each edge carries a provenance value that names the technique that found the call. jedi marks an edge from symbol resolution, and defuse marks an edge from the def-use linker.
Build the call graph
Section titled “Build the call graph”Call get_call_graph to get the graph of the whole project.
cg = analysis.get_call_graph()print(cg.number_of_nodes(), "callables,", cg.number_of_edges(), "edges")# 811 callables, 1250 edgesFind the callers of a method
Section titled “Find the callers of a method”get_callers finds every callable that calls the target method. The ('defuse',) value on the edge means that the def-use linker found this call.
callers = analysis.get_callers( target_class_name="src.flask.app.Flask", target_method_declaration="full_dispatch_request",)print(callers["target_method"])# src.flask.app.Flask.full_dispatch_request
for detail in callers["caller_details"]: print(detail["caller_signature"], detail["edge"]["provenance"])# src.flask.app.Flask.wsgi_app ('defuse',)Find the callees of a method
Section titled “Find the callees of a method”get_callees finds every callable that the source method calls. A signature that starts with can://flask/@external/ names a target outside the analyzed modules.
callees = analysis.get_callees( source_class_name="src.flask.app.Flask", source_method_declaration="full_dispatch_request",)for detail in callees["callee_details"]: print(detail["callee_signature"], detail["edge"]["provenance"])# can://flask/@external/_warnings/warn ('jedi',)# can://flask/@external/flask.app.Flask/dispatch_request ('jedi',)# can://flask/@external/flask.app.Flask/finalize_request ('jedi',)# can://flask/@external/flask.app.Flask/handle_user_exception ('jedi',)# can://flask/@external/flask.app.Flask/preprocess_request ('jedi',)# src.flask.app.Flask.dispatch_request ('defuse',)# src.flask.app.Flask.finalize_request ('defuse',)# src.flask.app.Flask.handle_user_exception ('defuse',)# src.flask.app.Flask.preprocess_request ('defuse',)# can://flask/@external/src.flask.signals.request_started/send ('defuse',)Compute reachability
Section titled “Compute reachability”The nodes of the graph are the callable signatures, so networkx answers reachability questions directly.
import networkx as nx
cg = analysis.get_call_graph()
source = "src.flask.app.Flask.wsgi_app"sink = "src.flask.app.Flask.dispatch_request"
print(nx.has_path(cg, source, sink))# True
edge = cg["src.flask.app.Flask.full_dispatch_request"][sink]print(edge["type"], edge["weight"], edge["provenance"])# CALL_DEP 1 ('defuse',)The PyClass and PyCallable models
Section titled “The PyClass and PyCallable models”CLDK returns typed model objects. Inspect these objects for the detail that the plain names do not carry.
Inspect a PyClass
Section titled “Inspect a PyClass”A PyClass object holds the methods, the base classes, and the decorators of the class.
pyclass = analysis.get_class("src.flask.app.Flask")print(pyclass.base_classes)# ['App']
print(sorted(pyclass.callables)[-4:])# ['test_request_context', 'update_template_context', 'url_for', 'wsgi_app']Inspect a PyCallable
Section titled “Inspect a PyCallable”A PyCallable object holds the signature, the parameters, the decorators, and the source span.
method = analysis.get_method("src.flask.sansio.scaffold.Scaffold", "route")print(method.name, method.kind)# route function
print(method.return_type)# t.Callable[[T_route], T_route]
print([decorator.name for decorator in method.decorators])# ['setupmethod']
print([(parameter.name, parameter.type) for parameter in method.parameters])# [('self', 'Scaffold'), ('rule', 'str'), ('options', 't.Any')]
print(method.start_line, method.end_line)# 344 373Read the source of a method
Section titled “Read the source of a method”get_method_bodies returns the source of each signature that you give it.
bodies = analysis.get_method_bodies(["src.flask.sansio.scaffold.Scaffold.route"])print(bodies["src.flask.sansio.scaffold.Scaffold.route"])# def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:# """Decorate a view function to register it with the given URL# rule and options. Calls :meth:`add_url_rule`, which has more# details about the implementation.# ...# """## def decorator(f: T_route) -> T_route:# endpoint = options.pop("endpoint", None)# self.add_url_rule(rule, endpoint, f, **options)# return f## return decoratorFind the subclasses of a class
Section titled “Find the subclasses of a class”get_sub_classes returns the classes that name the given class as a base class.
subclasses = analysis.get_sub_classes("src.flask.sansio.scaffold.Scaffold")for name in sorted(subclasses): print(name)# src.flask.sansio.blueprints.BlueprintGet the base classes
Section titled “Get the base classes”get_extended_classes returns the base class names. It gives the same list as the base_classes attribute of the class object.
bases = analysis.get_extended_classes("src.flask.app.Flask")print(bases)# ['App']Entry points
Section titled “Entry points”The backend marks the callables that code outside the project calls, such as CLI commands and route handlers. The get_entrypoints method is part of CLDK 2.0. The stable PyPI release is 1.5.0. Install the pre-release with pip install --pre cldk.
Find the framework entry points 2.0
Section titled “Find the framework entry points ”get_entrypoints returns one overview object for each callable that the backend marked as an entry point.
entrypoints = analysis.get_entrypoints()for entrypoint in entrypoints[:5]: print(entrypoint.signature)# src.flask.cli.shell_command# src.flask.cli.routes_command# examples.tutorial.flaskr.db.init_db_command# examples.tutorial.flaskr.blog.index