class Analyzer::Python::Flask

Defined in:

analyzer/analyzers/python/flask.cr

Constant Summary

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
DOTTED_REFERENCE_RE = /^#{DOT_NATION}$/
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.

INIT_APP_RE = /(#{PYTHON_VAR_NAME_REGEX})\.init_app\((#{PYTHON_VAR_NAME_REGEX})/
REGISTER_BLUEPRINT_RE = /(#{PYTHON_VAR_NAME_REGEX})\.register_blueprint\((#{DOT_NATION})/
REQUEST_PARAM_FIELD_PATTERNS = REQUEST_PARAM_FIELDS.map do |field_name, tuple| {tuple[1], Regex.new("request\\.#{field_name}\\[[rf]?['\"]([^'\"]*)['\"]\\]"), Regex.new("request\\.#{field_name}\\.get\\([rf]?['\"]([^'\"]*)['\"]")} end

extract_request_params runs once per route and used to rebuild two PCRE2 patterns per request field on every call (8 fields × 2 = 16 regex compilations per endpoint). PCRE2 JIT-compilation of an interpolated regex literal is ~3µs and dominated Flask scan time (profiling: ~50% of the analyzer). The field names are a fixed set, so precompile the access patterns once here and reuse them. Tuple shape: {noir_param_type, bracket_access_regex, get_access_regex}

REQUEST_PARAM_FIELDS = {"data" => {["POST", "PUT", "PATCH", "DELETE"], "form"}, "args" => {["GET"], "query"}, "form" => {["POST", "PUT", "PATCH", "DELETE"], "form"}, "files" => {["POST", "PUT", "PATCH", "DELETE"], "form"}, "values" => {["GET", "POST", "PUT", "PATCH", "DELETE"], "query"}, "json" => {["POST", "PUT", "PATCH", "DELETE"], "json"}, "cookies" => {nil, "cookie"}, "headers" => {nil, "header"}}

Reference: https://stackoverflow.com/a/16664376 Reference: https://tedboy.github.io/flask/generated/generated/flask.Request.html

REQUEST_PARAM_TYPES = {"query" => nil, "form" => ["POST", "PUT", "PATCH", "DELETE"], "json" => ["POST", "PUT", "PATCH", "DELETE"], "cookie" => nil, "header" => nil}
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)\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_KWARG_RE = /view_func=(#{DOT_NATION})(?:,|\)|$)/

Instance Method Summary

Instance methods inherited from class Analyzer::Python::PythonEngine

build_callees_from(body : String, body_start_line : Int32, path : String, *, definition_base_path : String | Nil = nil, source : String | Nil = nil) : Array(Callee) build_callees_from, find_def_line(lines : Array(String), decorator_line : Int32) : Int32 | Nil find_def_line, find_imported_modules(app_base_path : String, file_path : String, content : String | Nil = nil) : Hash(String, Tuple(String, Int32)) find_imported_modules, find_imported_package(package_path : String, dotted_as_names : String) : Array(Tuple(String, String, Int32)) find_imported_package, find_json_params(codeblock_lines : Array(String), json_var_names : Array(String)) : Array(Param) find_json_params, join_until_python_call_closes(lines : Array(String), index : Int32, line : String) : String join_until_python_call_closes, parse_code_block(data : String | Array(String), after : Regex | Nil = nil) : String | Nil parse_code_block, parse_function_def(source_lines : Array(String), start_index : Int32) : FunctionDefinition | Nil parse_function_def, push_callees_from(endpoint : Endpoint, body : String, body_start_line : Int32, path : String, *, definition_base_path : String | Nil = nil, source : String | Nil = nil) : Nil push_callees_from, python_paren_delta(line : String) : Int32 python_paren_delta, python_signature_line_span(lines : Array(String)) : Int32 python_signature_line_span, return_literal_value(data : String) : String return_literal_value

Class methods inherited from class Analyzer::Python::PythonEngine

python_test_path?(path : String, base_path : String | Nil = nil) : Bool python_test_path?

Instance methods inherited from class Analyzer

analyze analyze, base_path : String base_path, base_paths : Array(String) base_paths, callees_needed? : Bool callees_needed?, logger : NoirLogger logger, parallel_analyze(files : Array(String), &block : String -> Nil) parallel_analyze, read_file_content(path : String) : String read_file_content, result : Array(Endpoint) result, url : String url

Constructor methods inherited from class Analyzer

new(options : Hash(String, YAML::Any)) new

Macros inherited from class Analyzer

define_getter_methods(names) define_getter_methods

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files

Instance Method Detail

def analyze #

[View source]
def create_parser(path : String, content : String = "") : PythonParser #

Create a Python parser for a given path and content. The parser walks the file with tree-sitter and recursively absorbs globals from imported modules — no lexer step.


[View source]
def extract_params_from_decorator(path : String, lines : Array(String), line_index : Int32, direction : Symbol = :down) : Tuple(Array(Param), Int32) #

Extracts parameters from the decorator


[View source]
def get_endpoints(method : String, route_path : String, extra_params : String, codeblock_lines : Array(String), prefix : String) #

Extracts endpoint information from the given route and code block


[View source]
def get_filtered_params(method : String, params : Array(Param)) : Array(Param) #

Filters the parameters based on the HTTP method


[View source]
def get_parser(path : String, content : String = "") : PythonParser #

Get a parser for a given path


[View source]