bubble-analysis 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. bubble/__init__.py +3 -0
  2. bubble/cache.py +207 -0
  3. bubble/cli.py +470 -0
  4. bubble/config.py +52 -0
  5. bubble/detectors.py +90 -0
  6. bubble/enums.py +65 -0
  7. bubble/extractor.py +829 -0
  8. bubble/formatters.py +887 -0
  9. bubble/integrations/__init__.py +92 -0
  10. bubble/integrations/base.py +98 -0
  11. bubble/integrations/cli_scripts/__init__.py +49 -0
  12. bubble/integrations/cli_scripts/cli.py +108 -0
  13. bubble/integrations/cli_scripts/detector.py +149 -0
  14. bubble/integrations/django/__init__.py +63 -0
  15. bubble/integrations/django/cli.py +111 -0
  16. bubble/integrations/django/detector.py +331 -0
  17. bubble/integrations/django/semantics.py +40 -0
  18. bubble/integrations/fastapi/__init__.py +57 -0
  19. bubble/integrations/fastapi/cli.py +110 -0
  20. bubble/integrations/fastapi/detector.py +176 -0
  21. bubble/integrations/fastapi/semantics.py +14 -0
  22. bubble/integrations/flask/__init__.py +57 -0
  23. bubble/integrations/flask/cli.py +110 -0
  24. bubble/integrations/flask/detector.py +191 -0
  25. bubble/integrations/flask/semantics.py +19 -0
  26. bubble/integrations/formatters.py +268 -0
  27. bubble/integrations/generic/__init__.py +13 -0
  28. bubble/integrations/generic/config.py +106 -0
  29. bubble/integrations/generic/detector.py +346 -0
  30. bubble/integrations/generic/frameworks.py +145 -0
  31. bubble/integrations/models.py +68 -0
  32. bubble/integrations/queries.py +481 -0
  33. bubble/loader.py +118 -0
  34. bubble/models.py +397 -0
  35. bubble/propagation.py +737 -0
  36. bubble/protocols.py +104 -0
  37. bubble/queries.py +627 -0
  38. bubble/results.py +211 -0
  39. bubble/stubs.py +89 -0
  40. bubble/timing.py +144 -0
  41. bubble_analysis-0.2.0.dist-info/METADATA +264 -0
  42. bubble_analysis-0.2.0.dist-info/RECORD +46 -0
  43. bubble_analysis-0.2.0.dist-info/WHEEL +5 -0
  44. bubble_analysis-0.2.0.dist-info/entry_points.txt +2 -0
  45. bubble_analysis-0.2.0.dist-info/licenses/LICENSE +21 -0
  46. bubble_analysis-0.2.0.dist-info/top_level.txt +1 -0
bubble/config.py ADDED
@@ -0,0 +1,52 @@
1
+ """Configuration loading for flow analysis."""
2
+
3
+ import fnmatch
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ import yaml
9
+
10
+ ResolutionMode = Literal["strict", "default", "aggressive"]
11
+
12
+
13
+ @dataclass
14
+ class FlowConfig:
15
+ """Configuration for flow analysis."""
16
+
17
+ resolution_mode: ResolutionMode = "default"
18
+ exclude: list[str] = field(default_factory=list)
19
+ handled_base_classes: list[str] = field(default_factory=list)
20
+ async_boundaries: list[str] = field(default_factory=list)
21
+
22
+ def is_async_boundary(self, callee_name: str) -> bool:
23
+ """Check if a callee matches an async boundary pattern."""
24
+ for pattern in self.async_boundaries:
25
+ if fnmatch.fnmatch(callee_name, pattern):
26
+ return True
27
+ if "." in callee_name:
28
+ method_name = callee_name.split(".")[-1]
29
+ if fnmatch.fnmatch(method_name, pattern.lstrip("*.")):
30
+ return True
31
+ return False
32
+
33
+
34
+ def load_config(directory: Path) -> FlowConfig:
35
+ """Load configuration from .flow/config.yaml if it exists."""
36
+ config_path = directory / ".flow" / "config.yaml"
37
+ if not config_path.exists():
38
+ return FlowConfig()
39
+
40
+ with open(config_path) as f:
41
+ data = yaml.safe_load(f) or {}
42
+
43
+ mode = data.get("resolution_mode", "default")
44
+ if mode not in ("strict", "default", "aggressive"):
45
+ mode = "default"
46
+
47
+ return FlowConfig(
48
+ resolution_mode=mode,
49
+ exclude=data.get("exclude", []),
50
+ handled_base_classes=data.get("handled_base_classes", []),
51
+ async_boundaries=data.get("async_boundaries", []),
52
+ )
bubble/detectors.py ADDED
@@ -0,0 +1,90 @@
1
+ """Framework detectors for identifying entrypoints and patterns.
2
+
3
+ This module uses the generic detector with framework-specific configurations.
4
+ The generic detector produces identical results to the old framework-specific
5
+ detectors but with a single, configurable implementation.
6
+ """
7
+
8
+ from bubble.integrations.base import Entrypoint, GlobalHandler
9
+ from bubble.integrations.cli_scripts.detector import (
10
+ CLIEntrypointVisitor,
11
+ detect_cli_entrypoints,
12
+ )
13
+ from bubble.integrations.django.detector import (
14
+ DjangoExceptionHandlerVisitor,
15
+ DjangoViewVisitor,
16
+ )
17
+ from bubble.integrations.django.semantics import (
18
+ EXCEPTION_RESPONSES as DJANGO_EXCEPTION_RESPONSES,
19
+ )
20
+ from bubble.integrations.fastapi.detector import (
21
+ FastAPIExceptionHandlerVisitor,
22
+ FastAPIRouteVisitor,
23
+ )
24
+ from bubble.integrations.fastapi.semantics import (
25
+ EXCEPTION_RESPONSES as FASTAPI_EXCEPTION_RESPONSES,
26
+ )
27
+ from bubble.integrations.flask.detector import (
28
+ FlaskErrorHandlerVisitor,
29
+ FlaskRouteVisitor,
30
+ )
31
+ from bubble.integrations.flask.semantics import (
32
+ EXCEPTION_RESPONSES as FLASK_EXCEPTION_RESPONSES,
33
+ )
34
+ from bubble.integrations.generic import (
35
+ detect_entrypoints as generic_detect_entrypoints,
36
+ )
37
+ from bubble.integrations.generic import (
38
+ detect_global_handlers as generic_detect_global_handlers,
39
+ )
40
+ from bubble.integrations.generic.frameworks import (
41
+ DJANGO_CONFIG,
42
+ FASTAPI_CONFIG,
43
+ FLASK_CONFIG,
44
+ )
45
+
46
+ FRAMEWORK_EXCEPTION_RESPONSES: dict[str, dict[str, str]] = {
47
+ "django": DJANGO_EXCEPTION_RESPONSES,
48
+ "fastapi": FASTAPI_EXCEPTION_RESPONSES,
49
+ "flask": FLASK_EXCEPTION_RESPONSES,
50
+ }
51
+
52
+
53
+ def detect_entrypoints(source: str, file_path: str) -> list[Entrypoint]:
54
+ """Detect entrypoints in a Python source file (HTTP routes and CLI scripts).
55
+
56
+ Uses the generic detector with Flask, FastAPI, and Django configurations,
57
+ plus CLI script detection.
58
+ """
59
+ entrypoints: list[Entrypoint] = []
60
+ entrypoints.extend(generic_detect_entrypoints(source, file_path, FLASK_CONFIG))
61
+ entrypoints.extend(generic_detect_entrypoints(source, file_path, FASTAPI_CONFIG))
62
+ entrypoints.extend(generic_detect_entrypoints(source, file_path, DJANGO_CONFIG))
63
+ entrypoints.extend(detect_cli_entrypoints(source, file_path))
64
+ return entrypoints
65
+
66
+
67
+ def detect_global_handlers(source: str, file_path: str) -> list[GlobalHandler]:
68
+ """Detect global exception handlers in a Python source file.
69
+
70
+ Uses the generic detector with Flask, FastAPI, and Django configurations.
71
+ """
72
+ handlers: list[GlobalHandler] = []
73
+ handlers.extend(generic_detect_global_handlers(source, file_path, FLASK_CONFIG))
74
+ handlers.extend(generic_detect_global_handlers(source, file_path, FASTAPI_CONFIG))
75
+ handlers.extend(generic_detect_global_handlers(source, file_path, DJANGO_CONFIG))
76
+ return handlers
77
+
78
+
79
+ __all__ = [
80
+ "FRAMEWORK_EXCEPTION_RESPONSES",
81
+ "FlaskRouteVisitor",
82
+ "FlaskErrorHandlerVisitor",
83
+ "FastAPIRouteVisitor",
84
+ "FastAPIExceptionHandlerVisitor",
85
+ "DjangoViewVisitor",
86
+ "DjangoExceptionHandlerVisitor",
87
+ "CLIEntrypointVisitor",
88
+ "detect_entrypoints",
89
+ "detect_global_handlers",
90
+ ]
bubble/enums.py ADDED
@@ -0,0 +1,65 @@
1
+ """Typed enums for code flow analysis.
2
+
3
+ Centralizes all enum types to prevent magic string comparisons throughout the codebase.
4
+ All enums inherit from (str, Enum) to support JSON serialization and string comparison.
5
+ """
6
+
7
+ from enum import Enum
8
+
9
+
10
+ class EntrypointKind(str, Enum):
11
+ """Types of entrypoints where external input enters the program."""
12
+
13
+ HTTP_ROUTE = "http_route"
14
+ QUEUE_HANDLER = "queue_handler"
15
+ CLI_SCRIPT = "cli_script"
16
+ SCHEDULED_JOB = "scheduled_job"
17
+ TEST = "test"
18
+ UNKNOWN = "unknown"
19
+
20
+
21
+ class OutputFormat(str, Enum):
22
+ """Output format for CLI commands."""
23
+
24
+ JSON = "json"
25
+ TEXT = "text"
26
+
27
+
28
+ class ConfidenceLevel(str, Enum):
29
+ """Confidence level for exception propagation paths."""
30
+
31
+ HIGH = "high"
32
+ MEDIUM = "medium"
33
+ LOW = "low"
34
+
35
+
36
+ class Framework(str, Enum):
37
+ """Supported web frameworks."""
38
+
39
+ FLASK = "flask"
40
+ FASTAPI = "fastapi"
41
+ CLI = "cli"
42
+
43
+
44
+ class ResolutionMode(str, Enum):
45
+ """Resolution mode for call graph analysis."""
46
+
47
+ STRICT = "strict"
48
+ DEFAULT = "default"
49
+ AGGRESSIVE = "aggressive"
50
+
51
+
52
+ class ResolutionKind(str, Enum):
53
+ """How a call site was resolved to its target."""
54
+
55
+ IMPORT = "import"
56
+ SELF = "self"
57
+ CONSTRUCTOR = "constructor"
58
+ RETURN_TYPE = "return_type"
59
+ MODULE_ATTRIBUTE = "module_attribute"
60
+ NAME_FALLBACK = "name_fallback"
61
+ POLYMORPHIC = "polymorphic"
62
+ STUB = "stub"
63
+ UNRESOLVED = "unresolved"
64
+ FASTAPI_DEPENDS = "fastapi_depends"
65
+ IMPLICIT_DISPATCH = "implicit_dispatch"