aja-codeintel 0.1.9__tar.gz → 0.2.0__tar.gz

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 (108) hide show
  1. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/PKG-INFO +1 -1
  2. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/PKG-INFO +1 -1
  3. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/SOURCES.txt +5 -1
  4. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/__init__.py +2 -0
  5. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/endpoints_cmd.py +1 -1
  6. aja_codeintel-0.2.0/codeintel_cli/commands/project/entire_cmd.py +417 -0
  7. aja_codeintel-0.2.0/codeintel_cli/commands/project/trace_cmd.py +738 -0
  8. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/method_index.py +2 -2
  9. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/models.py +2 -15
  10. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/python/models.py +1 -14
  11. aja_codeintel-0.2.0/codeintel_cli/lang/shared_models.py +19 -0
  12. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/pyproject.toml +1 -1
  13. aja_codeintel-0.2.0/tests/test_entire_cmd.py +208 -0
  14. aja_codeintel-0.2.0/tests/test_trace_cmd.py +270 -0
  15. aja_codeintel-0.1.9/codeintel_cli/commands/project/trace_cmd.py +0 -473
  16. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/LICENSE +0 -0
  17. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/README.md +0 -0
  18. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/dependency_links.txt +0 -0
  19. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/entry_points.txt +0 -0
  20. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/requires.txt +0 -0
  21. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/aja_codeintel.egg-info/top_level.txt +0 -0
  22. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/__init__.py +0 -0
  23. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/__main__.py +0 -0
  24. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/cli.py +0 -0
  25. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/__init__.py +0 -0
  26. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/graph/__init__.py +0 -0
  27. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/graph/deps_cmd.py +0 -0
  28. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/graph/related_cmd.py +0 -0
  29. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/graph/relsymbols_cmd.py +0 -0
  30. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/graph/reverse_related_cmd.py +0 -0
  31. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/nav/__init__.py +0 -0
  32. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/nav/copy_cmd.py +0 -0
  33. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/nav/open_cmd.py +0 -0
  34. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/nav/where_cmd.py +0 -0
  35. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/context_cmd.py +0 -0
  36. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/debug_cmd.py +0 -0
  37. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/errors_cmd.py +0 -0
  38. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/fastapi_cmd.py +0 -0
  39. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/folder_cmd.py +0 -0
  40. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/frontend_cmd.py +0 -0
  41. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/imports_cmd.py +0 -0
  42. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/models_cmd.py +0 -0
  43. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/modeltree_cmd.py +0 -0
  44. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/new.py +0 -0
  45. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/overview_cmd.py +0 -0
  46. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/resolve_cmd.py +0 -0
  47. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/scan_cmd.py +0 -0
  48. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/servicemap_cmd.py +0 -0
  49. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/tree_cmd.py +0 -0
  50. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/types_cmd.py +0 -0
  51. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/commands/project/version_cmd.py +0 -0
  52. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/java_context.py +0 -0
  53. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/java_rel.py +0 -0
  54. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/java_service.py +0 -0
  55. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/python_context.py +0 -0
  56. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/python_rel.py +0 -0
  57. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/context/python_service.py +0 -0
  58. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/fuzzy.py +0 -0
  59. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/opener.py +0 -0
  60. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/project.py +0 -0
  61. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/resolve_folder.py +0 -0
  62. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/resolve_model_target.py +0 -0
  63. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/resolve_target.py +0 -0
  64. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/timing.py +0 -0
  65. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/core/where.py +0 -0
  66. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/db/__init__.py +0 -0
  67. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/db/cache.py +0 -0
  68. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/db/operations.py +0 -0
  69. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/db/schema.py +0 -0
  70. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/__init__.py +0 -0
  71. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/fastapi_scanner.py +0 -0
  72. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/java_spring.py +0 -0
  73. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/models.py +0 -0
  74. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/openapi_spec.py +0 -0
  75. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/python_web.py +0 -0
  76. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/endpoints/scan.py +0 -0
  77. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/errors.py +0 -0
  78. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/frontend/__init__.py +0 -0
  79. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/frontend/server.py +0 -0
  80. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/graph/__init__.py +0 -0
  81. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/graph/builder.py +0 -0
  82. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/graph/query.py +0 -0
  83. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/graph/traverse.py +0 -0
  84. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/__init__.py +0 -0
  85. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/__init__.py +0 -0
  86. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/ast_engine.py +0 -0
  87. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/auth.py +0 -0
  88. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/base_path.py +0 -0
  89. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/call_graph.py +0 -0
  90. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/engine.py +0 -0
  91. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/enums.py +0 -0
  92. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/pagination.py +0 -0
  93. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/resolve.py +0 -0
  94. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/types.py +0 -0
  95. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/java/validation.py +0 -0
  96. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/python/__init__.py +0 -0
  97. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/python/engine.py +0 -0
  98. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/lang/router.py +0 -0
  99. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/parser/imports.py +0 -0
  100. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/parser/resolve.py +0 -0
  101. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/parser/symbols.py +0 -0
  102. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/scanner/__init__.py +0 -0
  103. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/scanner/scanner.py +0 -0
  104. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/terminal/__init__.py +0 -0
  105. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/terminal/error_parser.py +0 -0
  106. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/codeintel_cli/terminal/printer.py +0 -0
  107. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/setup.cfg +0 -0
  108. {aja_codeintel-0.1.9 → aja_codeintel-0.2.0}/tests/test_fastapi_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -25,6 +25,7 @@ codeintel_cli/commands/project/__init__.py
25
25
  codeintel_cli/commands/project/context_cmd.py
26
26
  codeintel_cli/commands/project/debug_cmd.py
27
27
  codeintel_cli/commands/project/endpoints_cmd.py
28
+ codeintel_cli/commands/project/entire_cmd.py
28
29
  codeintel_cli/commands/project/errors_cmd.py
29
30
  codeintel_cli/commands/project/fastapi_cmd.py
30
31
  codeintel_cli/commands/project/folder_cmd.py
@@ -74,6 +75,7 @@ codeintel_cli/graph/query.py
74
75
  codeintel_cli/graph/traverse.py
75
76
  codeintel_cli/lang/__init__.py
76
77
  codeintel_cli/lang/router.py
78
+ codeintel_cli/lang/shared_models.py
77
79
  codeintel_cli/lang/java/__init__.py
78
80
  codeintel_cli/lang/java/ast_engine.py
79
81
  codeintel_cli/lang/java/auth.py
@@ -98,4 +100,6 @@ codeintel_cli/scanner/scanner.py
98
100
  codeintel_cli/terminal/__init__.py
99
101
  codeintel_cli/terminal/error_parser.py
100
102
  codeintel_cli/terminal/printer.py
101
- tests/test_fastapi_scanner.py
103
+ tests/test_entire_cmd.py
104
+ tests/test_fastapi_scanner.py
105
+ tests/test_trace_cmd.py
@@ -21,6 +21,7 @@ def register_project_commands(app: typer.Typer) -> None:
21
21
  from .debug_cmd import register_debug
22
22
  from .fastapi_cmd import register_fastapi_scan
23
23
  from .trace_cmd import register_trace
24
+ from .entire_cmd import register_entire
24
25
 
25
26
  register_overview(app)
26
27
  register_models(app)
@@ -40,3 +41,4 @@ def register_project_commands(app: typer.Typer) -> None:
40
41
  register_debug(app)
41
42
  register_fastapi_scan(app)
42
43
  register_trace(app)
44
+ register_entire(app)
@@ -14,7 +14,7 @@ from ...core.project import find_project_root
14
14
  from ...endpoints.java_spring import extract_spring_endpoints
15
15
  from ...endpoints.python_web import extract_python_endpoints
16
16
  from ...endpoints.scan import EndpointScanOptions, iter_supported_source_files
17
- from ...endpoints.openapi_spec import extract_openapi_endpoints # NEW
17
+ from ...endpoints.openapi_spec import extract_openapi_endpoints
18
18
  from ...errors import InvalidPathError
19
19
 
20
20
 
@@ -0,0 +1,417 @@
1
+ """
2
+ aja entire [path]
3
+
4
+ Full project intelligence report:
5
+ - Project tree (with .env, all files, inner functions, classes, models, DTOs)
6
+ - Every endpoint with FULL TRACE (import-aware, follows actual code flow)
7
+ - Request/Response models for each endpoint
8
+ - No guessing — traces through actual import chains
9
+
10
+ Even 130+ endpoints — every single one gets a proper trace.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import ast
15
+ import os
16
+ import re
17
+ import time
18
+ from collections import defaultdict
19
+ from dataclasses import dataclass, field
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ import typer
24
+
25
+ from ...core.project import find_project_root
26
+ from ...endpoints.fastapi_scanner import scan_fastapi_project, _collect_python_files, PydanticDTO
27
+ from .trace_cmd import (
28
+ _build_func_index,
29
+ _trace,
30
+ _render,
31
+ _file_role,
32
+ _short_path,
33
+ _get_ast,
34
+ _find_func,
35
+ _ast_cache,
36
+ FuncDef,
37
+ TraceNode,
38
+ )
39
+
40
+ # --------------------------------------------------------------------------- #
41
+ # Ignore dirs
42
+ # --------------------------------------------------------------------------- #
43
+
44
+ _SKIP_DIRS = {
45
+ ".git", ".idea", ".vscode", "__pycache__", ".pytest_cache",
46
+ ".ruff_cache", ".tox", ".venv", "venv", "env",
47
+ "node_modules", "dist", "build", "out", "target", ".gradle",
48
+ }
49
+
50
+ # --------------------------------------------------------------------------- #
51
+ # Tree: show .env, all source files with classes/functions inside
52
+ # --------------------------------------------------------------------------- #
53
+
54
+ def _walk_source_files(root: Path) -> list[Path]:
55
+ """Walk project and collect all .py files, .env, config files."""
56
+ files: list[Path] = []
57
+ stack = [root.resolve()]
58
+ while stack:
59
+ cur = stack.pop()
60
+ try:
61
+ for child in sorted(cur.iterdir(), key=lambda p: (p.is_file(), p.name.lower())):
62
+ if child.is_dir():
63
+ if child.name not in _SKIP_DIRS:
64
+ stack.append(child)
65
+ elif child.suffix in (".py", ".env", ".toml", ".yaml", ".yml", ".cfg", ".ini", ".json"):
66
+ files.append(child)
67
+ elif child.name in (".env", ".env.example", ".env.local", "Dockerfile", "docker-compose.yml"):
68
+ files.append(child)
69
+ except (PermissionError, FileNotFoundError):
70
+ continue
71
+ return files
72
+
73
+
74
+ def _extract_file_symbols(path: Path) -> dict:
75
+ """
76
+ Extract classes (with methods/fields) and top-level functions from a .py file.
77
+ Returns: { "classes": [...], "functions": [...] }
78
+ """
79
+ result = {"classes": [], "functions": []}
80
+ try:
81
+ src = path.read_text(encoding="utf-8", errors="ignore")
82
+ tree = ast.parse(src)
83
+ except Exception:
84
+ return result
85
+
86
+ for node in tree.body:
87
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
88
+ params = []
89
+ for arg in node.args.args:
90
+ if arg.arg == "self":
91
+ continue
92
+ ann = ast.unparse(arg.annotation) if arg.annotation else "Any"
93
+ params.append(f"{arg.arg}: {ann}")
94
+ ret = ast.unparse(node.returns) if node.returns else "None"
95
+ result["functions"].append({
96
+ "name": node.name,
97
+ "params": params,
98
+ "return": ret,
99
+ "line": node.lineno,
100
+ })
101
+ elif isinstance(node, ast.ClassDef):
102
+ cls_info = {
103
+ "name": node.name,
104
+ "bases": [ast.unparse(b) for b in node.bases],
105
+ "methods": [],
106
+ "fields": [],
107
+ "line": node.lineno,
108
+ }
109
+ for item in node.body:
110
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
111
+ params = []
112
+ for arg in item.args.args:
113
+ if arg.arg == "self":
114
+ continue
115
+ ann = ast.unparse(arg.annotation) if arg.annotation else "Any"
116
+ params.append(f"{arg.arg}: {ann}")
117
+ ret = ast.unparse(item.returns) if item.returns else "None"
118
+ cls_info["methods"].append({
119
+ "name": item.name,
120
+ "params": params,
121
+ "return": ret,
122
+ "line": item.lineno,
123
+ })
124
+ elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
125
+ field_type = ast.unparse(item.annotation) if item.annotation else "Any"
126
+ cls_info["fields"].append({
127
+ "name": item.target.id,
128
+ "type": field_type,
129
+ })
130
+ result["classes"].append(cls_info)
131
+
132
+ return result
133
+
134
+
135
+ def _print_project_tree(project_root: Path) -> None:
136
+ """Print project tree with .env files and all source files showing inner structure."""
137
+ typer.echo("=" * 70)
138
+ typer.echo(" PROJECT TREE (with classes, functions, models)")
139
+ typer.echo("=" * 70)
140
+ typer.echo(f" ROOT: {project_root}")
141
+ typer.echo("")
142
+
143
+ # Show .env files first
144
+ env_files = []
145
+ for pattern in (".env", ".env.example", ".env.local", ".env.development", ".env.production"):
146
+ f = project_root / pattern
147
+ if f.exists():
148
+ env_files.append(f)
149
+
150
+ if env_files:
151
+ typer.echo(" ╔══ ENVIRONMENT FILES")
152
+ for ef in env_files:
153
+ typer.echo(f" ║ {ef.name}")
154
+ try:
155
+ lines = ef.read_text(encoding="utf-8", errors="ignore").splitlines()
156
+ for line in lines:
157
+ line = line.strip()
158
+ if not line or line.startswith("#"):
159
+ continue
160
+ # Show key but mask sensitive values
161
+ if "=" in line:
162
+ key = line.split("=", 1)[0].strip()
163
+ typer.echo(f" ║ {key}=***")
164
+ else:
165
+ typer.echo(f" ║ {line}")
166
+ except Exception:
167
+ pass
168
+ typer.echo(" ╚══")
169
+ typer.echo("")
170
+
171
+ # Walk and show source structure
172
+ def _walk_tree(cur: Path, prefix: str = "", depth: int = 0, max_depth: int = 10) -> None:
173
+ if depth > max_depth:
174
+ return
175
+ try:
176
+ entries = sorted(cur.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
177
+ except (PermissionError, FileNotFoundError):
178
+ return
179
+
180
+ visible = []
181
+ for e in entries:
182
+ if e.name in _SKIP_DIRS:
183
+ continue
184
+ if e.is_dir():
185
+ visible.append(e)
186
+ elif e.suffix in (".py",) or e.name in (".env",):
187
+ visible.append(e)
188
+
189
+ for i, entry in enumerate(visible):
190
+ is_last = i == len(visible) - 1
191
+ branch = "└── " if is_last else "├── "
192
+ child_prefix = prefix + (" " if is_last else "│ ")
193
+
194
+ if entry.is_dir():
195
+ typer.echo(f" {prefix}{branch}{entry.name}/")
196
+ _walk_tree(entry, child_prefix, depth + 1, max_depth)
197
+ else:
198
+ typer.echo(f" {prefix}{branch}{entry.name}")
199
+ # Show inner structure for .py files
200
+ if entry.suffix == ".py":
201
+ symbols = _extract_file_symbols(entry)
202
+ inner_items = []
203
+ for cls in symbols["classes"]:
204
+ bases = f"({', '.join(cls['bases'])})" if cls['bases'] else ""
205
+ inner_items.append(("class", f"{cls['name']}{bases}", cls))
206
+ for fn in symbols["functions"]:
207
+ inner_items.append(("func", f"{fn['name']}()", fn))
208
+
209
+ for j, (kind, label, info) in enumerate(inner_items):
210
+ is_inner_last = j == len(inner_items) - 1
211
+ inner_branch = "└─ " if is_inner_last else "├─ "
212
+ inner_prefix = child_prefix + (" " if is_inner_last else "│ ")
213
+
214
+ if kind == "class":
215
+ typer.echo(f" {child_prefix}{inner_branch}class {label}")
216
+ # Show fields and methods
217
+ members = []
218
+ for fld in info.get("fields", []):
219
+ members.append(f"{fld['name']}: {fld['type']}")
220
+ for meth in info.get("methods", []):
221
+ members.append(f"{meth['name']}()")
222
+ for k, mem in enumerate(members[:15]): # limit to 15
223
+ mem_last = k == len(members[:15]) - 1
224
+ mem_branch = "└─ " if mem_last else "├─ "
225
+ typer.echo(f" {inner_prefix}{mem_branch}{mem}")
226
+ else:
227
+ typer.echo(f" {child_prefix}{inner_branch}def {label}")
228
+
229
+ _walk_tree(project_root)
230
+ typer.echo("")
231
+
232
+
233
+ # --------------------------------------------------------------------------- #
234
+ # DTO / Model display helpers
235
+ # --------------------------------------------------------------------------- #
236
+
237
+ def _format_dto(dto: PydanticDTO, indent: str = " ") -> list[str]:
238
+ """Format a Pydantic DTO with all fields."""
239
+ lines = []
240
+ bases = f"({', '.join(dto.bases)})" if dto.bases else ""
241
+ lines.append(f"{indent}{dto.name}{bases}")
242
+ for f in dto.fields:
243
+ req = "*" if f.required else " "
244
+ default = f" = {f.default}" if f.default else ""
245
+ lines.append(f"{indent} {req} {f.name}: {f.type}{default}")
246
+ return lines
247
+
248
+
249
+ # --------------------------------------------------------------------------- #
250
+ # ENTIRE command: traces every endpoint
251
+ # --------------------------------------------------------------------------- #
252
+
253
+ def register_entire(app: typer.Typer) -> None:
254
+ @app.command("entire", help="Full project report: tree + every endpoint traced with request/response.")
255
+ def entire(
256
+ path: str = typer.Argument(".", help="Project root folder"),
257
+ depth: int = typer.Option(6, "--depth", "-d", help="Max trace depth per endpoint"),
258
+ no_tree: bool = typer.Option(False, "--no-tree", help="Skip project tree section"),
259
+ ) -> None:
260
+ _ast_cache.clear()
261
+ t0 = time.perf_counter()
262
+
263
+ folder = Path(path).resolve()
264
+ if not folder.exists() or not folder.is_dir():
265
+ typer.echo(f" Invalid folder: {folder}")
266
+ raise typer.Exit(1)
267
+
268
+ project_root = find_project_root(folder)
269
+
270
+ # ── 1. Project Tree ────────────────────────────────────────────
271
+ if not no_tree:
272
+ _print_project_tree(project_root)
273
+
274
+ # ── 2. Scan all endpoints and models ───────────────────────────
275
+ all_py = _collect_python_files(project_root, _SKIP_DIRS)
276
+ endpoints, pydantic_dtos, sqla_models, request_models, response_models, endpoint_deps = \
277
+ scan_fastapi_project(project_root, files=all_py)
278
+
279
+ # Build indexes
280
+ func_index = _build_func_index(all_py)
281
+
282
+ # DTO lookup by name
283
+ dto_by_name: dict[str, PydanticDTO] = {d.name: d for d in pydantic_dtos}
284
+
285
+ # De-duplicate endpoints
286
+ seen_keys: set[tuple[str, str]] = set()
287
+ unique_endpoints = []
288
+ for e in endpoints:
289
+ k = (e.method, e.path)
290
+ if k not in seen_keys:
291
+ seen_keys.add(k)
292
+ unique_endpoints.append(e)
293
+ unique_endpoints.sort(key=lambda e: (e.path, e.method))
294
+
295
+ # ── 3. MODELS / DTOs Section ──────────────────────────────────
296
+ typer.echo("=" * 70)
297
+ typer.echo(" MODELS & DTOs")
298
+ typer.echo("=" * 70)
299
+ typer.echo("")
300
+
301
+ if sqla_models:
302
+ typer.echo(" ── SQLAlchemy Models ──")
303
+ for m in sqla_models:
304
+ typer.echo(f" {m.name} (table: {m.table_name}) {m.file}")
305
+ for col in m.columns:
306
+ pk = " [PK]" if col.primary_key else ""
307
+ fk = f" [FK→{col.foreign_key}]" if col.foreign_key else ""
308
+ typer.echo(f" {col.name}: {col.col_type}{pk}{fk}")
309
+ for rel in m.relationships:
310
+ typer.echo(f" →{rel.name}: {rel.target}")
311
+ typer.echo("")
312
+
313
+ if pydantic_dtos:
314
+ # Classify
315
+ req_dtos = [d for d in pydantic_dtos if d.name in request_models and d.name not in response_models]
316
+ res_dtos = [d for d in pydantic_dtos if d.name in response_models and d.name not in request_models]
317
+ both_dtos = [d for d in pydantic_dtos if d.name in request_models and d.name in response_models]
318
+ other_dtos = [d for d in pydantic_dtos if d.name not in request_models and d.name not in response_models]
319
+
320
+ for label, group in [("REQUEST DTOs", req_dtos), ("RESPONSE DTOs", res_dtos),
321
+ ("REQUEST+RESPONSE", both_dtos), ("SHARED/CONFIG", other_dtos)]:
322
+ if not group:
323
+ continue
324
+ typer.echo(f" ── {label} ({len(group)}) ──")
325
+ for dto in group:
326
+ for line in _format_dto(dto):
327
+ typer.echo(line)
328
+ typer.echo("")
329
+
330
+ # ── 4. ENDPOINT TRACES ────────────────────────────────────────
331
+ typer.echo("=" * 70)
332
+ typer.echo(f" ENDPOINT TRACES ({len(unique_endpoints)} endpoints)")
333
+ typer.echo("=" * 70)
334
+ typer.echo("")
335
+
336
+ for idx, ep in enumerate(unique_endpoints, 1):
337
+ handler_name = ep.handler
338
+ handler_file = Path(ep.file)
339
+
340
+ typer.echo(f" {'━' * 66}")
341
+ typer.echo(f" [{idx:03d}] {ep.method} {ep.path}")
342
+ typer.echo(f" {'━' * 66}")
343
+ typer.echo(f" Handler : {handler_name}() · {_short_path(handler_file, project_root)}")
344
+
345
+ # Show request/response models for this endpoint
346
+ req_model = getattr(ep, 'request_body', None) or None
347
+ resp_model = getattr(ep, 'response_model', None) or None
348
+
349
+ if req_model:
350
+ typer.echo(f" Request : {req_model}")
351
+ if req_model in dto_by_name:
352
+ dto = dto_by_name[req_model]
353
+ for f in dto.fields:
354
+ req_mark = "*" if f.required else " "
355
+ typer.echo(f" {req_mark} {f.name}: {f.type}")
356
+
357
+ if resp_model:
358
+ typer.echo(f" Response: {resp_model}")
359
+ # Try to resolve the actual type (strip list[], Optional[], etc.)
360
+ inner = re.sub(r"^(?:list|List)\[(.+)\]$", r"\1", resp_model)
361
+ inner = re.sub(r"^Optional\[(.+)\]$", r"\1", inner)
362
+ if inner in dto_by_name:
363
+ dto = dto_by_name[inner]
364
+ for f in dto.fields:
365
+ typer.echo(f" {f.name}: {f.type}")
366
+
367
+ # Show dependencies
368
+ ep_deps = None
369
+ for dep_item in endpoint_deps:
370
+ if dep_item["method"] == ep.method and dep_item["path"] == ep.path:
371
+ ep_deps = dep_item["depends"]
372
+ break
373
+ if ep_deps:
374
+ typer.echo(f" Depends : {', '.join(ep_deps)}")
375
+
376
+ # Trace the call chain
377
+ tree = _get_ast(handler_file)
378
+ if tree is None:
379
+ typer.echo(f" ⚠ Could not parse {handler_file.name}")
380
+ typer.echo("")
381
+ continue
382
+
383
+ handler_node = _find_func(tree, handler_name)
384
+ if handler_node is None:
385
+ typer.echo(f" ⚠ Function '{handler_name}' not found in {handler_file.name}")
386
+ typer.echo("")
387
+ continue
388
+
389
+ visited: set[tuple[str, str]] = set()
390
+ root_node = _trace(handler_node, handler_file, func_index, project_root,
391
+ visited, depth=0, max_depth=depth)
392
+
393
+ typer.echo(f" Call chain:")
394
+ typer.echo(f" {handler_name}()")
395
+ child_prefix = " "
396
+ for i, child in enumerate(root_node.calls):
397
+ for line in _render(child, project_root, child_prefix, i == len(root_node.calls) - 1):
398
+ typer.echo(line)
399
+
400
+ if root_node.unresolved:
401
+ typer.echo(f" Unresolved: {', '.join(root_node.unresolved)}")
402
+
403
+ typer.echo("")
404
+
405
+ # ── 5. Summary ────────────────────────────────────────────────
406
+ elapsed = time.perf_counter() - t0
407
+ typer.echo("=" * 70)
408
+ typer.echo(f" SUMMARY")
409
+ typer.echo("=" * 70)
410
+ typer.echo(f" Total endpoints : {len(unique_endpoints)}")
411
+ typer.echo(f" Total DTOs : {len(pydantic_dtos)}")
412
+ typer.echo(f" Request models : {len(request_models)}")
413
+ typer.echo(f" Response models : {len(response_models)}")
414
+ typer.echo(f" SQLAlchemy models : {len(sqla_models)}")
415
+ typer.echo(f" Python files : {len(all_py)}")
416
+ typer.echo(f" Finished in : {elapsed:.2f}s")
417
+ typer.echo("")