archapi 0.3.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.
@@ -0,0 +1,237 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List
5
+
6
+ from archapi.frameworks.generic import GenericAdapter
7
+ from archapi.types import APIPlan, APIGenome, GeneratedFile, ScanResult, ValidationReport
8
+
9
+
10
+ class ExpressTypeScriptAdapter(GenericAdapter):
11
+ name = "express-typescript"
12
+
13
+ def scan(self, project_path: Path) -> ScanResult:
14
+ result = super().scan(project_path)
15
+ result.framework = self.name
16
+ return result
17
+
18
+ def extract_genome(self, maps: Dict[str, Any], scan_result: ScanResult) -> APIGenome:
19
+ genome = super().extract_genome(maps, scan_result)
20
+ genome.framework = self.name
21
+
22
+ package_json = scan_result.project_path / "package.json"
23
+ package_text = ""
24
+ if package_json.exists():
25
+ package_text = package_json.read_text(
26
+ encoding="utf-8",
27
+ errors="ignore",
28
+ ).lower()
29
+
30
+ if "zod" in package_text:
31
+ genome.schema_style = "zod"
32
+ elif "joi" in package_text:
33
+ genome.schema_style = "joi"
34
+
35
+ if "jest" in package_text and "supertest" in package_text:
36
+ genome.test_style = "jest-supertest"
37
+
38
+ genome.route_style = "express-router" if scan_result.routes else "unknown"
39
+ genome.controller_style = "express-controller" if scan_result.controllers else "unknown"
40
+ genome.service_style = "service-layer" if scan_result.services else "unknown"
41
+ genome.metadata["language"] = "typescript"
42
+ genome.metadata["project_path"] = str(scan_result.project_path)
43
+
44
+ required_layers = [
45
+ bool(scan_result.routes),
46
+ bool(scan_result.controllers),
47
+ bool(scan_result.services),
48
+ ]
49
+
50
+ has_express_package = "express" in package_text
51
+
52
+ if not any(required_layers):
53
+ genome.confidence = min(genome.confidence, 0.10)
54
+ elif not has_express_package:
55
+ # If route/controller/service layers are present but package.json is missing
56
+ # at project root, allow generation with warning-level confidence.
57
+ # This supports config-hint mode for monorepos or unusual layouts.
58
+ genome.confidence = min(genome.confidence, 0.65)
59
+ genome.metadata["package_json_warning"] = "Express package.json not found at project root."
60
+
61
+ return genome
62
+
63
+ def generate_code(
64
+ self,
65
+ plan: APIPlan,
66
+ genome: APIGenome,
67
+ maps: Dict[str, Any],
68
+ ) -> List[GeneratedFile]:
69
+ if not plan.generation_allowed:
70
+ return []
71
+
72
+ entity = plan.entities[-1] if plan.entities else "Resource"
73
+ entity_pascal = entity[0].upper() + entity[1:]
74
+ entity_lower = entity_pascal[0].lower() + entity_pascal[1:]
75
+ entity_file = entity_lower.lower()
76
+
77
+ route_dir = self._output_dir(maps, "route_map", "src/routes")
78
+ controller_dir = self._output_dir(maps, "controller_map", "src/controllers")
79
+ service_dir = self._output_dir(maps, "service_map", "src/services")
80
+ schema_dir = self._output_dir(maps, "schema_map", "src/schemas")
81
+ test_dir = self._output_dir(maps, "test_map", "tests")
82
+
83
+ route_path = route_dir / f"{entity_file}.routes.ts"
84
+ controller_path = controller_dir / f"{entity_file}.controller.ts"
85
+ service_path = service_dir / f"{entity_file}.service.ts"
86
+ schema_path = schema_dir / f"{entity_file}.schema.ts"
87
+ test_path = test_dir / f"{entity_file}.test.ts"
88
+
89
+ express_path = (
90
+ plan.path
91
+ .replace("{user_id}", ":userId")
92
+ .replace("{product_id}", ":productId")
93
+ .replace("{id}", ":id")
94
+ )
95
+
96
+ status_code = int(plan.metadata.get("response_status", 200))
97
+ action = plan.metadata.get("action", "unknown")
98
+
99
+ route_content = f"""import {{ Router, Request, Response, NextFunction }} from "express";
100
+ import {{ {entity_lower}Controller }} from "../controllers/{entity_file}.controller";
101
+ import {{ {entity_lower}RequestSchema }} from "../schemas/{entity_file}.schema";
102
+
103
+ const router = Router();
104
+
105
+ function validate{entity_pascal}Request(req: Request, res: Response, next: NextFunction) {{
106
+ const parsed = {entity_lower}RequestSchema.safeParse({{
107
+ params: req.params,
108
+ query: req.query,
109
+ body: req.body,
110
+ }});
111
+
112
+ if (!parsed.success) {{
113
+ return res.status(400).json({{ errors: parsed.error.flatten() }});
114
+ }}
115
+
116
+ return next();
117
+ }}
118
+
119
+ router.{plan.method.lower()}("{express_path}", validate{entity_pascal}Request, {entity_lower}Controller.handle);
120
+
121
+ export default router;
122
+ """
123
+
124
+ controller_content = f"""import {{ Request, Response, NextFunction }} from "express";
125
+ import {{ {entity_lower}Service }} from "../services/{entity_file}.service";
126
+
127
+ export const {entity_lower}Controller = {{
128
+ async handle(req: Request, res: Response, next: NextFunction) {{
129
+ try {{
130
+ const result = await {entity_lower}Service.execute({{
131
+ params: req.params,
132
+ query: req.query,
133
+ body: req.body,
134
+ }});
135
+
136
+ return res.status({status_code}).json({{ data: result }});
137
+ }} catch (error) {{
138
+ return next(error);
139
+ }}
140
+ }},
141
+ }};
142
+ """
143
+
144
+ service_content = f"""type {entity_pascal}ServiceInput = {{
145
+ params: Record<string, unknown>;
146
+ query: Record<string, unknown>;
147
+ body: unknown;
148
+ }};
149
+
150
+ export const {entity_lower}Service = {{
151
+ async execute(input: {entity_pascal}ServiceInput) {{
152
+ // TODO: Replace this placeholder with project-specific business logic.
153
+ return {{
154
+ message: "{entity_pascal} API placeholder response",
155
+ action: "{action}",
156
+ params: input.params,
157
+ query: input.query,
158
+ body: input.body,
159
+ }};
160
+ }},
161
+ }};
162
+ """
163
+
164
+ schema_content = f"""import {{ z }} from "zod";
165
+
166
+ export const {entity_lower}RequestSchema = z.object({{
167
+ params: z.record(z.unknown()).optional(),
168
+ query: z.record(z.unknown()).optional(),
169
+ body: z.unknown().optional(),
170
+ }});
171
+
172
+ export type {entity_pascal}Request = z.infer<typeof {entity_lower}RequestSchema>;
173
+ """
174
+
175
+ test_content = f"""describe("{entity_pascal} API", () => {{
176
+ it("should have a generated placeholder test", () => {{
177
+ // TODO: Replace this placeholder with a real Supertest request.
178
+ expect(true).toBe(true);
179
+ }});
180
+ }});
181
+ """
182
+
183
+ return [
184
+ GeneratedFile(route_path, route_content),
185
+ GeneratedFile(controller_path, controller_content),
186
+ GeneratedFile(service_path, service_content),
187
+ GeneratedFile(schema_path, schema_content),
188
+ GeneratedFile(test_path, test_content),
189
+ ]
190
+
191
+ def _output_dir(self, maps: Dict[str, Any], map_key: str, fallback: str) -> Path:
192
+ values = maps.get(map_key, {})
193
+ project_path = Path(maps.get("_project_path", "."))
194
+
195
+ if isinstance(values, dict) and values:
196
+ first_path = Path(next(iter(values.values()))).resolve()
197
+ parent = first_path.parent
198
+
199
+ try:
200
+ return parent.relative_to(project_path)
201
+ except ValueError:
202
+ return Path(fallback)
203
+
204
+ return Path(fallback)
205
+
206
+ def validate_generated_code(
207
+ self,
208
+ files: List[GeneratedFile],
209
+ plan: APIPlan,
210
+ genome: APIGenome,
211
+ ) -> ValidationReport:
212
+ errors: List[str] = []
213
+ warnings: List[str] = []
214
+
215
+ if not plan.generation_allowed:
216
+ errors.append(plan.reason or "Generation not allowed.")
217
+
218
+ required_layers = ["routes", "controllers", "services", "schemas", "tests"]
219
+ generated_paths = [str(file.path) for file in files]
220
+
221
+ for layer in required_layers:
222
+ if not any(layer in path for path in generated_paths):
223
+ errors.append(f"Missing generated {layer} layer.")
224
+
225
+ for file in files:
226
+ if not file.content.strip():
227
+ errors.append(f"Generated file is empty: {file.path}")
228
+
229
+ if Path(file.path).exists():
230
+ warnings.append(
231
+ f"Generated file path already exists relative to current directory: {file.path}"
232
+ )
233
+
234
+ if genome.confidence < 0.75:
235
+ warnings.append("Architecture confidence is moderate; review generated files before applying.")
236
+
237
+ return ValidationReport(success=not errors, errors=errors, warnings=warnings)
@@ -0,0 +1,184 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List
5
+
6
+ from archapi.frameworks.generic import GenericAdapter
7
+ from archapi.types import APIPlan, APIGenome, GeneratedFile, ScanResult, ValidationReport
8
+
9
+
10
+ class FastAPIAdapter(GenericAdapter):
11
+ name = "fastapi"
12
+
13
+ def scan(self, project_path: Path) -> ScanResult:
14
+ result = super().scan(project_path)
15
+ result.framework = self.name
16
+ return result
17
+
18
+ def extract_genome(self, maps: Dict[str, Any], scan_result: ScanResult) -> APIGenome:
19
+ genome = super().extract_genome(maps, scan_result)
20
+ genome.framework = self.name
21
+
22
+ req = scan_result.project_path / "requirements.txt"
23
+ pyproject = scan_result.project_path / "pyproject.toml"
24
+
25
+ dep_text = ""
26
+ if req.exists():
27
+ dep_text += req.read_text(encoding="utf-8", errors="ignore").lower()
28
+ if pyproject.exists():
29
+ dep_text += pyproject.read_text(encoding="utf-8", errors="ignore").lower()
30
+
31
+ genome.route_style = "fastapi-apirouter" if scan_result.routes else "unknown"
32
+ genome.controller_style = "fastapi-endpoint" if scan_result.controllers or scan_result.routes else "unknown"
33
+ genome.service_style = "service-layer" if scan_result.services else "unknown"
34
+ genome.schema_style = "pydantic" if "pydantic" in dep_text or scan_result.schemas else "unknown"
35
+ genome.test_style = "pytest" if "pytest" in dep_text or scan_result.tests else "unknown"
36
+ genome.metadata["language"] = "python"
37
+ genome.metadata["project_path"] = str(scan_result.project_path)
38
+
39
+ has_fastapi_dependency = "fastapi" in dep_text
40
+
41
+ if not scan_result.routes and not scan_result.services:
42
+ genome.confidence = min(genome.confidence, 0.10)
43
+ elif not has_fastapi_dependency:
44
+ genome.confidence = min(genome.confidence, 0.65)
45
+ genome.metadata["dependency_warning"] = "FastAPI dependency not found at project root."
46
+
47
+ return genome
48
+
49
+ def generate_code(
50
+ self,
51
+ plan: APIPlan,
52
+ genome: APIGenome,
53
+ maps: Dict[str, Any],
54
+ ) -> List[GeneratedFile]:
55
+ if not plan.generation_allowed:
56
+ return []
57
+
58
+ entity = plan.entities[-1] if plan.entities else "Resource"
59
+ entity_pascal = entity[0].upper() + entity[1:]
60
+ entity_lower = entity_pascal[0].lower() + entity_pascal[1:]
61
+ entity_file = entity_lower.lower()
62
+
63
+ router_dir = self._output_dir(maps, "route_map", "app/routers")
64
+ service_dir = self._output_dir(maps, "service_map", "app/services")
65
+ schema_dir = self._output_dir(maps, "schema_map", "app/schemas")
66
+ test_dir = self._output_dir(maps, "test_map", "tests")
67
+
68
+ router_path = router_dir / f"{entity_file}_router.py"
69
+ service_path = service_dir / f"{entity_file}_service.py"
70
+ schema_path = schema_dir / f"{entity_file}_schema.py"
71
+ test_path = test_dir / f"test_{entity_file}.py"
72
+
73
+ method = plan.method.lower()
74
+ status_code = int(plan.metadata.get("response_status", 200))
75
+ action = plan.metadata.get("action", "unknown")
76
+
77
+ router_content = f'''from fastapi import APIRouter, HTTPException
78
+ from app.schemas.{entity_file}_schema import {entity_pascal}Request, {entity_pascal}Response
79
+ from app.services.{entity_file}_service import {entity_lower}_service
80
+
81
+ router = APIRouter()
82
+
83
+
84
+ @router.{method}("{plan.path}", response_model={entity_pascal}Response, status_code={status_code})
85
+ async def handle_{entity_lower}(request: {entity_pascal}Request = None):
86
+ try:
87
+ result = await {entity_lower}_service.execute(request)
88
+ return result
89
+ except Exception as exc:
90
+ raise HTTPException(status_code=500, detail=str(exc))
91
+ '''
92
+
93
+ service_content = f'''from app.schemas.{entity_file}_schema import {entity_pascal}Request, {entity_pascal}Response
94
+
95
+
96
+ class {entity_pascal}Service:
97
+ async def execute(self, request: {entity_pascal}Request | None) -> {entity_pascal}Response:
98
+ # TODO: Replace this placeholder with project-specific business logic.
99
+ return {entity_pascal}Response(
100
+ message="{entity_pascal} API placeholder response",
101
+ action="{action}",
102
+ )
103
+
104
+
105
+ {entity_lower}_service = {entity_pascal}Service()
106
+ '''
107
+
108
+ schema_content = f'''from pydantic import BaseModel
109
+ from typing import Optional, Any, Dict
110
+
111
+
112
+ class {entity_pascal}Request(BaseModel):
113
+ params: Optional[Dict[str, Any]] = None
114
+ query: Optional[Dict[str, Any]] = None
115
+ body: Optional[Any] = None
116
+
117
+
118
+ class {entity_pascal}Response(BaseModel):
119
+ message: str
120
+ action: str
121
+ '''
122
+
123
+ test_content = f'''def test_{entity_lower}_generated_placeholder():
124
+ # TODO: Replace this placeholder with a real TestClient request.
125
+ assert True
126
+ '''
127
+
128
+ return [
129
+ GeneratedFile(router_path, router_content),
130
+ GeneratedFile(service_path, service_content),
131
+ GeneratedFile(schema_path, schema_content),
132
+ GeneratedFile(test_path, test_content),
133
+ ]
134
+
135
+ def _output_dir(self, maps: Dict[str, Any], map_key: str, fallback: str) -> Path:
136
+ values = maps.get(map_key, {})
137
+ project_path = Path(maps.get("_project_path", "."))
138
+
139
+ if isinstance(values, dict) and values:
140
+ first_path = Path(next(iter(values.values()))).resolve()
141
+ parent = first_path.parent
142
+
143
+ try:
144
+ return parent.relative_to(project_path)
145
+ except ValueError:
146
+ return Path(fallback)
147
+
148
+ return Path(fallback)
149
+
150
+ def validate_generated_code(
151
+ self,
152
+ files: List[GeneratedFile],
153
+ plan: APIPlan,
154
+ genome: APIGenome,
155
+ ) -> ValidationReport:
156
+ errors: List[str] = []
157
+ warnings: List[str] = []
158
+
159
+ if not plan.generation_allowed:
160
+ errors.append(plan.reason or "Generation not allowed.")
161
+
162
+ required_suffixes = ["_router.py", "_service.py", "_schema.py"]
163
+ generated_paths = [str(file.path) for file in files]
164
+
165
+ for suffix in required_suffixes:
166
+ if not any(path.endswith(suffix) for path in generated_paths):
167
+ errors.append(f"Missing generated FastAPI layer: {suffix}")
168
+
169
+ if not any("/test_" in path or path.startswith("tests/test_") for path in generated_paths):
170
+ errors.append("Missing generated FastAPI test layer.")
171
+
172
+ for file in files:
173
+ if not file.content.strip():
174
+ errors.append(f"Generated file is empty: {file.path}")
175
+
176
+ if Path(file.path).exists():
177
+ warnings.append(
178
+ f"Generated file path already exists relative to current directory: {file.path}"
179
+ )
180
+
181
+ if genome.confidence < 0.75:
182
+ warnings.append("Architecture confidence is moderate; review generated files before applying.")
183
+
184
+ return ValidationReport(success=not errors, errors=errors, warnings=warnings)
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List
5
+
6
+ from archapi.frameworks.base import FrameworkAdapter
7
+ from archapi.planning.intent_planner import IntentPlanner
8
+ from archapi.types import (
9
+ APIPlan,
10
+ APIGenome,
11
+ DetectionResult,
12
+ GeneratedFile,
13
+ ScanResult,
14
+ ValidationReport,
15
+ )
16
+
17
+
18
+ IGNORED_DIRS = {
19
+ "node_modules",
20
+ ".git",
21
+ "dist",
22
+ "build",
23
+ "coverage",
24
+ ".venv",
25
+ "__pycache__",
26
+ "vendor",
27
+ "target",
28
+ ".archapi",
29
+ "sample_projects",
30
+ "archapi.egg-info",
31
+ }
32
+
33
+
34
+ class GenericAdapter(FrameworkAdapter):
35
+ name = "generic"
36
+
37
+ def detect(self, project_path: Path) -> DetectionResult:
38
+ return DetectionResult("generic", 0.10, ["Fallback generic adapter"])
39
+
40
+ def scan(self, project_path: Path) -> ScanResult:
41
+ result = ScanResult(framework=self.name, project_path=project_path)
42
+
43
+ for path in self._walk_files(project_path):
44
+ lower = str(path).lower()
45
+
46
+ if "route" in lower or "url" in lower:
47
+ result.routes.append(path)
48
+ elif "controller" in lower or "handler" in lower or "view" in lower:
49
+ result.controllers.append(path)
50
+ elif "service" in lower:
51
+ result.services.append(path)
52
+ elif "model" in lower or "entity" in lower:
53
+ result.models.append(path)
54
+ elif "schema" in lower or "serializer" in lower or "dto" in lower:
55
+ result.schemas.append(path)
56
+ elif "middleware" in lower or "permission" in lower or "auth" in lower:
57
+ result.middleware.append(path)
58
+ elif "test" in lower or "spec" in lower:
59
+ result.tests.append(path)
60
+ elif path.name in {
61
+ "package.json",
62
+ "pyproject.toml",
63
+ "requirements.txt",
64
+ "pom.xml",
65
+ "build.gradle",
66
+ "go.mod",
67
+ "composer.json",
68
+ "Gemfile",
69
+ }:
70
+ result.config_files.append(path)
71
+ else:
72
+ result.unknown.append(path)
73
+
74
+ return result
75
+
76
+ def build_maps(self, scan_result: ScanResult) -> Dict[str, Any]:
77
+ return {
78
+ "_project_path": str(scan_result.project_path),
79
+ "file_map": {
80
+ str(path.relative_to(scan_result.project_path)): str(path)
81
+ for path in (
82
+ scan_result.routes
83
+ + scan_result.controllers
84
+ + scan_result.services
85
+ + scan_result.models
86
+ + scan_result.schemas
87
+ + scan_result.middleware
88
+ + scan_result.tests
89
+ )
90
+ },
91
+ "route_map": self._name_map(scan_result.routes),
92
+ "controller_map": self._name_map(scan_result.controllers),
93
+ "service_map": self._name_map(scan_result.services),
94
+ "model_map": self._name_map(scan_result.models),
95
+ "schema_map": self._name_map(scan_result.schemas),
96
+ "middleware_map": self._name_map(scan_result.middleware),
97
+ "test_map": self._name_map(scan_result.tests),
98
+ }
99
+
100
+ def extract_genome(self, maps: Dict[str, Any], scan_result: ScanResult) -> APIGenome:
101
+ confidence = 0.0
102
+ confidence += 0.20 if scan_result.routes else 0
103
+ confidence += 0.20 if scan_result.controllers else 0
104
+ confidence += 0.20 if scan_result.services else 0
105
+ confidence += 0.15 if scan_result.models else 0
106
+ confidence += 0.15 if scan_result.schemas else 0
107
+ confidence += 0.10 if scan_result.tests else 0
108
+
109
+ return APIGenome(
110
+ framework=self.name,
111
+ route_style="detected" if scan_result.routes else "unknown",
112
+ controller_style="detected" if scan_result.controllers else "unknown",
113
+ service_style="detected" if scan_result.services else "unknown",
114
+ model_style="detected" if scan_result.models else "unknown",
115
+ schema_style="detected" if scan_result.schemas else "unknown",
116
+ auth_style="detected" if scan_result.middleware else "unknown",
117
+ test_style="detected" if scan_result.tests else "unknown",
118
+ confidence=round(confidence, 2),
119
+ )
120
+
121
+ def plan_api(self, request: str, genome: APIGenome, maps: Dict[str, Any]) -> APIPlan:
122
+ intent = IntentPlanner().plan(request)
123
+
124
+ generation_allowed = genome.confidence >= 0.45
125
+ reason = None if generation_allowed else "Architecture confidence too low; returning plan only."
126
+
127
+ return APIPlan(
128
+ request=request,
129
+ method=intent.method,
130
+ path=intent.path,
131
+ entities=intent.entities,
132
+ layers=["route", "controller", "service", "schema", "test"],
133
+ generation_allowed=generation_allowed,
134
+ reason=reason,
135
+ metadata={
136
+ "adapter": self.name,
137
+ "resource": intent.resource,
138
+ "action": intent.action,
139
+ "response_status": intent.response_status,
140
+ **intent.metadata,
141
+ },
142
+ )
143
+
144
+ def generate_code(
145
+ self,
146
+ plan: APIPlan,
147
+ genome: APIGenome,
148
+ maps: Dict[str, Any],
149
+ ) -> List[GeneratedFile]:
150
+ if not plan.generation_allowed:
151
+ return []
152
+
153
+ entity = plan.entities[-1] if plan.entities else "Resource"
154
+ lower = entity.lower()
155
+
156
+ return [
157
+ GeneratedFile(
158
+ path=Path(f"generated/{lower}_api.txt"),
159
+ content=(
160
+ "# Generated API plan\n"
161
+ f"method: {plan.method}\n"
162
+ f"path: {plan.path}\n"
163
+ f"entity: {entity}\n"
164
+ "note: Generic framework fallback was used.\n"
165
+ ),
166
+ )
167
+ ]
168
+
169
+ def validate_generated_code(
170
+ self,
171
+ files: List[GeneratedFile],
172
+ plan: APIPlan,
173
+ genome: APIGenome,
174
+ ) -> ValidationReport:
175
+ errors = []
176
+ warnings = []
177
+
178
+ if not plan.generation_allowed:
179
+ errors.append(plan.reason or "Generation not allowed.")
180
+
181
+ if plan.generation_allowed and not files:
182
+ errors.append("No files generated.")
183
+
184
+ for file in files:
185
+ if not file.content.strip():
186
+ errors.append(f"Generated file is empty: {file.path}")
187
+
188
+ return ValidationReport(success=not errors, errors=errors, warnings=warnings)
189
+
190
+ def _walk_files(self, root: Path) -> List[Path]:
191
+ files = []
192
+ root = Path(root)
193
+
194
+ for path in root.rglob("*"):
195
+ if path.is_dir():
196
+ continue
197
+
198
+ try:
199
+ rel_parts = path.relative_to(root).parts
200
+ except ValueError:
201
+ rel_parts = path.parts
202
+
203
+ if any(part in IGNORED_DIRS for part in rel_parts):
204
+ continue
205
+
206
+ files.append(path)
207
+
208
+ return files
209
+
210
+ def _name_map(self, paths: List[Path]) -> Dict[str, str]:
211
+ return {path.stem: str(path) for path in paths}
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from archapi.frameworks.generic import GenericAdapter
4
+ from archapi.frameworks.express_ts.adapter import ExpressTypeScriptAdapter
5
+ from archapi.frameworks.fastapi_adapter import FastAPIAdapter
6
+
7
+
8
+ class FrameworkRegistry:
9
+ def __init__(self) -> None:
10
+ generic = GenericAdapter()
11
+
12
+ self._adapters = {
13
+ "generic": generic,
14
+ "express-typescript": ExpressTypeScriptAdapter(),
15
+ "nestjs": generic,
16
+ "fastapi": FastAPIAdapter(),
17
+ "django-drf": generic,
18
+ "flask": generic,
19
+ "spring-boot": generic,
20
+ "dotnet-core": generic,
21
+ "laravel": generic,
22
+ "rails": generic,
23
+ "go-api": generic,
24
+ "node-unknown": generic,
25
+ }
26
+
27
+ def get(self, framework: str):
28
+ return self._adapters.get(framework, self._adapters["generic"])
File without changes
File without changes
File without changes