class

Analyzer::Python::Flask

Inherits Analyzer::Python::FlaskFamily < Analyzer::Python::PythonEngine < Analyzer < FileHelper < Reference < Object

Constants

ADD_RESOURCE_RE = /(#{PYTHON_VAR_NAME_REGEX})\s*\.\s*add_resource\s*\((.+)\)/m

flask_restful / flask-restx register class-based Resources with api.add_resource(ResourceClass, "/url"[, "/url2"], endpoint=...). Each Resource exposes one endpoint per HTTP-verb method it defines. Hoisted for the common (alias-free) case so the per-file regex isn't recompiled; ADD_RESOURCE_SUBSTRING is the cheap per-line guard.

ADD_RESOURCE_SUBSTRINGS = [".add_resource("]
ADD_URL_RULE_RE = /(#{PYTHON_VAR_NAME_REGEX})\.add_url_rule\((.+)\)/m
COMPETING_FRAMEWORK_IMPORT_RE = /^\s*(?:from|import)\s+(?:fastapi|sanic|litestar|starlette|quart|robyn|ninja|bottle|aiohttp|falcon|pyramid|tornado)\b/m

Decorator-based Python web frameworks whose route syntax is indistinguishable from Flask's at the line level — @app.get("/x"), @api.post("/x"). A file that imports one of these and never mentions flask belongs to that framework's analyzer. ninja (Django Ninja), bottle and aiohttp were missing from the list, so the Flask analyzer claimed their handler files and relabelled the routes python_flask with Flask-shaped (usually empty) params.

CONFIG_CLASS_RE = /\Aclass\s+(\w+)\s*(?:\(([^)]*)\))?\s*:/
CONFIG_URL_PREFIX_RE = /url_prefix\s*=\s*#{DOT_NATION}\.config\s*(?:\[\s*['"](\w+)['"]\s*\]|\.get\s*\(\s*['"](\w+)['"]\s*(?:,\s*[rf]?['"]([^'"]*)['"])?)/

url_prefix=app.config["X"] / app.config.get("X", "/d"): the strings the project's config modules assign X, else the .get default. Which config class is live is read, not guessed from file order: the class config.from_object(...) names (or the base it inherits X from), else module-level settings and the base/default classes (Config, BaseConfig, DefaultConfig, one others inherit from). Values still in conflict each give a row.

DOTTED_REFERENCE_RE = /^#{DOT_NATION}$/
FAB_API_KIND = "api"

Flask-AppBuilder composes a URL from a class-level base and the method-level @expose(...) suffix; reading only the suffix reports /data where the route is /api/v1/chart/data. The base is (flask_appbuilder/baseviews.py:281, api/__init__.py:562):

BaseView subclass : route_base, else "/" + class_name.lower() BaseApi subclass : route_base, else "/api/<version>/<resource_name>", with resource_name defaulting to class_name.lower() and version defaulting to "v1"

route_base and resource_name are separate knobs: route_base wins outright and is NOT prefixed with /api/<version>/, while resource_name only fills the last segment of the API default. Both are ordinary class attributes, so a subclass inherits them — Superset's ChartDataRestApi(ChartRestApi) takes resource_name = "chart" from a base class in another file.

The chain therefore has to be walked, and it has to terminate somewhere: this table is the terminator, holding every Flask-AppBuilder class that can serve as a base together with the attributes FAB itself sets on it. IndexView/AuthView/UtilView pin route_base = "" (so SupersetIndexView really is mounted at /, not /supersetindexview), and the security APIs pin resource names that no application source spells out (GroupApi -> security/groups). Generated from Flask-AppBuilder 5.2.2, plus BaseModelApi, which 3.4.x and 4.5.x carry and 5.2.2 dropped; every value here is identical across those three releases.

Tuple shape: {kind, route_base, resource_name, version}. nil means "not set at this level, keep walking".

FAB_BUILTIN_CLASSES = {"ApiKeyApi" => {FAB_API_KIND, nil, "security/api_keys", "v1"}, "AuthDBView" => {FAB_VIEW_KIND, "", nil, nil}, "AuthLDAPView" => {FAB_VIEW_KIND, "", nil, nil}, "AuthOAuthView" => {FAB_VIEW_KIND, "", nil, nil}, "AuthRemoteUserView" => {FAB_VIEW_KIND, "", nil, nil}, "AuthSAMLView" => {FAB_VIEW_KIND, "", nil, nil}, "AuthView" => {FAB_VIEW_KIND, "", nil, nil}, "BaseApi" => {FAB_API_KIND, nil, nil, "v1"}, "BaseCRUDView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseFormView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseModelApi" => {FAB_API_KIND, nil, nil, "v1"}, "BaseModelView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseRegisterUser" => {FAB_VIEW_KIND, "/register", nil, nil}, "BaseSimpleDirectChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseSimpleGroupByChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "BaseView" => {FAB_VIEW_KIND, nil, nil, nil}, "ChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "CompactCRUDMixin" => {FAB_VIEW_KIND, nil, nil, nil}, "DirectByChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "DirectChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "GroupApi" => {FAB_API_KIND, nil, "security/groups", "v1"}, "GroupByChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "IndexView" => {FAB_VIEW_KIND, "", nil, nil}, "LocaleView" => {FAB_VIEW_KIND, "/lang", nil, nil}, "MasterDetailView" => {FAB_VIEW_KIND, nil, nil, nil}, "MenuApi" => {FAB_API_KIND, nil, "menu", "v1"}, "ModelRestApi" => {FAB_API_KIND, nil, nil, "v1"}, "ModelView" => {FAB_VIEW_KIND, nil, nil, nil}, "MultipleView" => {FAB_VIEW_KIND, nil, nil, nil}, "OpenApi" => {FAB_API_KIND, "/api", nil, "v1"}, "PermissionApi" => {FAB_API_KIND, nil, "security/permissions", "v1"}, "PermissionModelView" => {FAB_VIEW_KIND, "/permissions", nil, nil}, "PermissionViewMenuApi" => {FAB_API_KIND, nil, "security/permissions-resources", "v1"}, "PermissionViewModelView" => {FAB_VIEW_KIND, "/permissionviews", nil, nil}, "PublicFormView" => {FAB_VIEW_KIND, nil, nil, nil}, "RegisterUserDBView" => {FAB_VIEW_KIND, "/register", nil, nil}, "RegisterUserModelView" => {FAB_VIEW_KIND, "/registeruser", nil, nil}, "RegisterUserOAuthView" => {FAB_VIEW_KIND, "/register", nil, nil}, "ResetMyPasswordView" => {FAB_VIEW_KIND, "/resetmypassword", nil, nil}, "ResetPasswordView" => {FAB_VIEW_KIND, "/resetpassword", nil, nil}, "RoleApi" => {FAB_API_KIND, nil, "security/roles", "v1"}, "RoleModelView" => {FAB_VIEW_KIND, "/roles", nil, nil}, "SecurityApi" => {FAB_API_KIND, nil, "security", "v1"}, "SimpleFormView" => {FAB_VIEW_KIND, nil, nil, nil}, "SwaggerView" => {FAB_VIEW_KIND, "/swagger", nil, nil}, "TimeChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "UserApi" => {FAB_API_KIND, nil, "security/users", "v1"}, "UserDBModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserGroupModelView" => {FAB_VIEW_KIND, "/groups", nil, nil}, "UserInfoEditView" => {FAB_VIEW_KIND, nil, nil, nil}, "UserLDAPModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserOAuthModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserRemoteUserModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserSAMLModelView" => {FAB_VIEW_KIND, "/users", nil, nil}, "UserStatsChartView" => {FAB_VIEW_KIND, nil, nil, nil}, "UtilView" => {FAB_VIEW_KIND, "", nil, nil}, "ViewMenuApi" => {FAB_API_KIND, nil, "security/resources", "v1"}, "ViewMenuModelView" => {FAB_VIEW_KIND, "/viewmenus", nil, nil}}
FAB_CLASS_DEF_RE = /^class\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(([^)]*)\))?\s*:/

class Foo(Bar, Baz): — the base list is optional (class Foo:).

FAB_FROM_IMPORT_RE = /^\s*from\s+flask_appbuilder(?:\.[A-Za-z0-9_.]+)?\s+import\s+(.+)$/

from flask_appbuilder[.x] import A, B as C / import flask_appbuilder. The import graph resolves names to files inside the scanned project, so a third-party base class never resolves through it; this maps the local name back to the Flask-AppBuilder name that FAB_BUILTIN_CLASSES keys on (BaseView as AppBuilderBaseView, the Airflow-plugin spelling).

FAB_MODEL_REST_ROUTES = [{"get_list", "GET", "/"}, {"post", "POST", "/"}, {"get", "GET", "/<pk>"}, {"put", "PUT", "/<pk>"}, {"delete", "DELETE", "/<pk>"}, {"info", "GET", "/_info"}]

The routes ModelRestApi serves on its own, by the method name include_route_methods / exclude_route_methods select them with (flask_appbuilder/api/__init__.py).

FAB_REGISTER_RE = /\.add_(?:api|view|view_no_menu)\s*\(\s*([A-Za-z_][\w.]*)|['"]view['"]\s*:\s*([A-Za-z_][\w.]*)/

appbuilder.add_api(X) / add_view(X, ...), and an Airflow plugin's appbuilder_views = [{"view": X()}].

FAB_ROUTE_METHOD_SETS = {"REST_MODEL_VIEW_CRUD_SET" => ["delete", "get", "get_list", "post", "put", "info"] of ::String, "CRUD_SET" => ["add", "list", "edit", "delete", "action_post", "show"] of ::String, "RELATED_VIEW_SET" => ["add", "list", "edit", "delete"] of ::String, "API_SET" => ["api_create", "api_delete", "api_get", "api_read", "api_update"] of ::String, "IMPORT" => ["import_"] of ::String}

Route-method sets as Superset's RouteMethod spells them; any other RouteMethod.NAME is the lowercased name.

FAB_ROUTE_METHODS_ATTR_RE = /^(include_route_methods|exclude_route_methods)\s*(?::[^=]+)?=\s*(.+)$/m
FAB_VIEW_KIND = "view"
FLASK_INSTANCE_RE = /(#{PYTHON_VAR_NAME_REGEX})(?::#{DOT_NATION})?=(?:flask\.)?Flask\(/

Per-line route-discovery patterns. These interpolate only the PYTHON_VAR_NAME_REGEX/DOT_NATION constants, so the inline literals were recompiling identical PCRE2 patterns on every source line of every file (analyze runs them per line). Compile once here; the .to_s expansion of the interpolated constants is byte-identical to the previous inline form, so matching behaviour is unchanged.

FORM_BIND_RE = /(?<![\w.])([A-Za-z_][\w.]*)\(\s*(?:formdata\s*=\s*)?request\.form\b/

Form(request.form): a WTForms form bound to the posted form data.

FORM_CALL_RE = /(?<![\w.])([A-Za-z_][\w.]*)\((?!(?:[^()\n]|\([^()\n]*\))*\bformdata\s*=)(?!\s*request\.)/

LoginForm(): a Flask-WTF form binds the posted form data itself, unless handed its source (formdata=None, request.args).

FORM_FIELD_RE = /^\s+([A-Za-z_]\w*)\s*=\s*(?:[A-Za-z_]\w*\.)*\w*Field\s*\(/
FROM_OBJECT_RE = /\.from_object\(\s*(?:['"]([\w.]+)['"]|([\w.]+)\s*\))/
INCLUDE_ROUTER_RE = /\.include_router\((#{DOT_NATION})/

fastopenapi's <router>.include_router(child, prefix="/x"). The receiver is often an expression (Dify's cast(..., router)), so only the child is captured.

INIT_APP_RE = /(#{PYTHON_VAR_NAME_REGEX})\.init_app\((#{PYTHON_VAR_NAME_REGEX})/
KEYS_CALL_RE = /(?<![\w.])([A-Za-z_][\w.]*)\(\s*([A-Za-z_]\w*)\s*,\s*[(\[]((?:\s*[rbu]?['"][^'"]*['"]\s*,?)+)[)\]]/

helper(body, ("a", "b")): a JSON body handed to a call with a literal tuple or list of key names.

PLAIN_ROUTE_LITERAL_RE = /(?<![\w%])[rR]?(['"])[^'"\\]*\1(?!\s*(?:%|\.|\+|\[))/
REGISTER_BLUEPRINT_RE = /(#{PYTHON_VAR_NAME_REGEX})\.register_blueprint\((#{DOT_NATION})/
REQUEST_MEMBERSHIP_RE = /['"]([^'"]+)['"]\s+(?:not\s+)?in\s+request\.(\w+)\b/
RESOURCE_REGISTRAR_DEF_RE = /^(\s*)(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*self\s*,\s*resource\b/

def add_x(self, resource, ...) head of an Api-subclass method that may wrap add_resource (e.g. redash's add_org_resource).

ROUTE_DECORATOR_RE = /^\s*@\s*#{DOT_NATION}\s*\.\s*(?:route|get|post|put|patch|delete|head|options|trace|query)\s*\(/m

Source-ownership and add_url_rule helpers — same constant-only interpolation (DOT_NATION), hoisted so the per-file/per-call sites don't recompile them. VIEW_FUNC_KWARG_RE has no whitespace tolerance (unlike Quart's) because Flask's add_url_rule args are matched on space-stripped lines.

ROUTE_REGISTRAR_RE = /\b#{DOT_NATION}\s*\.\s*(?:add_url_rule|register_blueprint)\s*\(/
VIEW_ASSIGN_RE = /(#{PYTHON_VAR_NAME_REGEX})(?::#{DOT_NATION})?=(#{PYTHON_VAR_NAME_REGEX})\.as_view\(/
VIEW_FUNC_AS_VIEW_RE = /view_func=(#{PYTHON_VAR_NAME_REGEX})\.as_view\([rf]?['"]([^'"]*)['"][,)]/

view_func= extractors for the add_url_rule scan below — same constant-only interpolation, hoisted so they aren't rebuilt on every add_url_rule(...) match found in a file.

VIEW_FUNC_KWARG_RE = /view_func=(#{DOT_NATION})(?:,|\)|$)/
VIEW_FUNC_VAR_RE = /view_func=(#{PYTHON_VAR_NAME_REGEX})[,\)]/
VIEW_REGISTRATION_CALL_RE = /(?:^|[^\w.])(#{DOT_NATION})\s*\(\s*(#{PYTHON_VAR_NAME_REGEX})\s*,(.*\broutes\s*=.*)\)/m

helper(bp, ..., routes=[...], ...): the helper, the router it is handed first, and the argument list.

VIEW_REGISTRATION_HEAD_RE = /\A#{DOT_NATION}\((?:#{PYTHON_VAR_NAME_REGEX},|\z)/

A statement that opens a call handing a name first: helper(bp, ... or helper( with the arguments on the next lines (space-stripped).

Class methods

tech_name
Source

Instance methods

analyze
Source
extract_params_from_decorator(path : String, lines : Array(String), line_index : Int32, direction : Symbol = :down) : Tuple(Array(Param), Int32)

Extracts parameters from the decorator

Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source

Nested types