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.
archapi/types.py ADDED
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ @dataclass
9
+ class DetectionResult:
10
+ framework: str
11
+ confidence: float
12
+ reasons: List[str] = field(default_factory=list)
13
+
14
+
15
+ @dataclass
16
+ class ScanResult:
17
+ framework: str
18
+ project_path: Path
19
+ routes: List[Path] = field(default_factory=list)
20
+ controllers: List[Path] = field(default_factory=list)
21
+ services: List[Path] = field(default_factory=list)
22
+ models: List[Path] = field(default_factory=list)
23
+ schemas: List[Path] = field(default_factory=list)
24
+ middleware: List[Path] = field(default_factory=list)
25
+ tests: List[Path] = field(default_factory=list)
26
+ config_files: List[Path] = field(default_factory=list)
27
+ unknown: List[Path] = field(default_factory=list)
28
+
29
+
30
+ @dataclass
31
+ class APIGenome:
32
+ framework: str
33
+ route_style: str = "unknown"
34
+ controller_style: str = "unknown"
35
+ service_style: str = "unknown"
36
+ model_style: str = "unknown"
37
+ schema_style: str = "unknown"
38
+ auth_style: str = "unknown"
39
+ error_style: str = "unknown"
40
+ test_style: str = "unknown"
41
+ confidence: float = 0.0
42
+ metadata: Dict[str, Any] = field(default_factory=dict)
43
+
44
+
45
+ @dataclass
46
+ class APIPlan:
47
+ request: str
48
+ method: str
49
+ path: str
50
+ entities: List[str]
51
+ layers: List[str]
52
+ generation_allowed: bool
53
+ reason: Optional[str] = None
54
+ metadata: Dict[str, Any] = field(default_factory=dict)
55
+
56
+
57
+ @dataclass
58
+ class GeneratedFile:
59
+ path: Path
60
+ content: str
61
+ action: str = "create"
62
+
63
+
64
+ @dataclass
65
+ class ValidationReport:
66
+ success: bool
67
+ errors: List[str] = field(default_factory=list)
68
+ warnings: List[str] = field(default_factory=list)
69
+
70
+
71
+ @dataclass
72
+ class GenerationResult:
73
+ project_path: Path
74
+ plan: APIPlan
75
+ files: List[GeneratedFile]
76
+ validation_report: ValidationReport
77
+ warnings: List[str] = field(default_factory=list)
78
+
79
+ @property
80
+ def diff(self) -> str:
81
+ chunks: List[str] = []
82
+ for file in self.files:
83
+ chunks.append(f"--- {file.path}")
84
+ chunks.append(f"+++ {file.path}")
85
+ chunks.append(file.content)
86
+ chunks.append("")
87
+ return "\n".join(chunks)
88
+
89
+ def apply(self) -> None:
90
+ if not self.validation_report.success:
91
+ raise RuntimeError(
92
+ "Cannot apply generated files because validation failed: "
93
+ + "; ".join(self.validation_report.errors)
94
+ )
95
+
96
+ for generated in self.files:
97
+ target = self.project_path / generated.path
98
+ target.parent.mkdir(parents=True, exist_ok=True)
99
+
100
+ if target.exists() and generated.action == "create":
101
+ raise FileExistsError(f"Refusing to overwrite existing file: {target}")
102
+
103
+ target.write_text(generated.content, encoding="utf-8")
File without changes
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List
5
+
6
+ from archapi.types import APIGenome, GeneratedFile
7
+
8
+
9
+ @dataclass
10
+ class ArchitectureScore:
11
+ score: float
12
+ max_score: float
13
+ percentage: float
14
+ passed: Dict[str, bool] = field(default_factory=dict)
15
+ notes: List[str] = field(default_factory=list)
16
+
17
+
18
+ class ArchitectureConsistencyScorer:
19
+ """
20
+ Scores whether generated API files follow the detected project architecture.
21
+
22
+ v0.3 supports Express TypeScript and FastAPI layer conventions.
23
+ """
24
+
25
+ def score(self, files: List[GeneratedFile], genome: APIGenome) -> ArchitectureScore:
26
+ generated_paths = [str(file.path).replace("\\", "/") for file in files]
27
+ generated_text = "\n".join(file.content for file in files)
28
+
29
+ if genome.framework == "fastapi":
30
+ checks = self._score_fastapi(generated_paths, generated_text, genome)
31
+ else:
32
+ checks = self._score_express(generated_paths, generated_text, genome)
33
+
34
+ max_score = float(len(checks))
35
+ raw_score = float(sum(1 for value in checks.values() if value))
36
+ percentage = round((raw_score / max_score) * 100, 2) if max_score else 0.0
37
+
38
+ notes = [
39
+ f"Failed architecture check: {name}"
40
+ for name, passed in checks.items()
41
+ if not passed
42
+ ]
43
+
44
+ return ArchitectureScore(
45
+ score=raw_score,
46
+ max_score=max_score,
47
+ percentage=percentage,
48
+ passed=checks,
49
+ notes=notes,
50
+ )
51
+
52
+ def _score_express(self, paths: List[str], text: str, genome: APIGenome) -> Dict[str, bool]:
53
+ return {
54
+ "has_route_layer": any("/routes/" in p or p.startswith("src/routes/") for p in paths),
55
+ "has_controller_layer": any("/controllers/" in p or p.startswith("src/controllers/") for p in paths),
56
+ "has_service_layer": any("/services/" in p or p.startswith("src/services/") for p in paths),
57
+ "has_schema_layer": any("/schemas/" in p or p.startswith("src/schemas/") for p in paths),
58
+ "has_test_layer": any("/tests/" in p or p.startswith("tests/") for p in paths),
59
+ "matches_express_router": genome.framework != "express-typescript" or "Router" in text,
60
+ "matches_controller_style": genome.controller_style == "unknown" or "Request" in text or "Response" in text,
61
+ "matches_service_style": genome.service_style == "unknown" or "Service" in text or "service" in text,
62
+ "matches_schema_style": genome.schema_style == "unknown" or genome.schema_style not in {"zod", "joi"} or genome.schema_style in text.lower(),
63
+ "matches_test_style": genome.test_style == "unknown" or "describe(" in text or "it(" in text,
64
+ }
65
+
66
+ def _score_fastapi(self, paths: List[str], text: str, genome: APIGenome) -> Dict[str, bool]:
67
+ return {
68
+ "has_router_layer": any("/routers/" in p or p.startswith("app/routers/") for p in paths),
69
+ "has_service_layer": any("/services/" in p or p.startswith("app/services/") for p in paths),
70
+ "has_schema_layer": any("/schemas/" in p or p.startswith("app/schemas/") for p in paths),
71
+ "has_test_layer": any("/tests/" in p or p.startswith("tests/") for p in paths),
72
+ "matches_apirouter": "APIRouter" in text,
73
+ "matches_async_endpoint": "async def" in text,
74
+ "matches_pydantic_schema": "BaseModel" in text,
75
+ "matches_service_style": "Service" in text or "_service" in text,
76
+ "matches_test_style": "def test_" in text,
77
+ }
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ from archapi.types import GeneratedFile, ValidationReport
7
+
8
+
9
+ class BasicGeneratedCodeValidator:
10
+ """
11
+ Framework-independent validation for generated file objects.
12
+ """
13
+
14
+ def validate(self, files: List[GeneratedFile]) -> ValidationReport:
15
+ errors: List[str] = []
16
+ warnings: List[str] = []
17
+
18
+ if not files:
19
+ errors.append("No generated files were provided.")
20
+
21
+ seen = set()
22
+
23
+ for generated in files:
24
+ if not generated.content.strip():
25
+ errors.append(f"Generated file is empty: {generated.path}")
26
+
27
+ if str(generated.path) in seen:
28
+ errors.append(f"Duplicate generated path: {generated.path}")
29
+
30
+ seen.add(str(generated.path))
31
+
32
+ if Path(generated.path).is_absolute():
33
+ errors.append(f"Generated path must be relative, got absolute path: {generated.path}")
34
+
35
+ if ".." in Path(generated.path).parts:
36
+ errors.append(f"Generated path must not contain '..': {generated.path}")
37
+
38
+ return ValidationReport(success=len(errors) == 0, errors=errors, warnings=warnings)
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Dict, List, Optional
8
+
9
+
10
+ @dataclass
11
+ class CommandResult:
12
+ name: str
13
+ command: List[str]
14
+ skipped: bool
15
+ success: bool
16
+ returncode: Optional[int] = None
17
+ stdout: str = ""
18
+ stderr: str = ""
19
+ reason: Optional[str] = None
20
+
21
+
22
+ @dataclass
23
+ class CommandValidationReport:
24
+ success: bool
25
+ results: List[CommandResult] = field(default_factory=list)
26
+
27
+ @property
28
+ def errors(self) -> List[str]:
29
+ messages: List[str] = []
30
+ for result in self.results:
31
+ if not result.skipped and not result.success:
32
+ messages.append(
33
+ f"{result.name} failed with exit code {result.returncode}"
34
+ )
35
+ return messages
36
+
37
+
38
+ class CommandValidator:
39
+ """
40
+ Runs project validation commands only when they are available.
41
+
42
+ v0.2 supports Node/TypeScript package.json scripts:
43
+ - build
44
+ - test
45
+ - lint
46
+ """
47
+
48
+ def __init__(self, project_path: Path):
49
+ self.project_path = Path(project_path)
50
+
51
+ def validate_node_project(self) -> CommandValidationReport:
52
+ package_json = self.project_path / "package.json"
53
+
54
+ if not package_json.exists():
55
+ return CommandValidationReport(
56
+ success=True,
57
+ results=[
58
+ CommandResult(
59
+ name="node_project",
60
+ command=[],
61
+ skipped=True,
62
+ success=True,
63
+ reason="package.json not found",
64
+ )
65
+ ],
66
+ )
67
+
68
+ try:
69
+ data = json.loads(package_json.read_text(encoding="utf-8"))
70
+ except Exception as exc:
71
+ return CommandValidationReport(
72
+ success=False,
73
+ results=[
74
+ CommandResult(
75
+ name="package_json_parse",
76
+ command=[],
77
+ skipped=False,
78
+ success=False,
79
+ reason=f"Failed to parse package.json: {exc}",
80
+ )
81
+ ],
82
+ )
83
+
84
+ scripts: Dict[str, str] = data.get("scripts", {})
85
+
86
+ checks = [
87
+ ("build", ["npm", "run", "build"]),
88
+ ("test", ["npm", "test"]),
89
+ ("lint", ["npm", "run", "lint"]),
90
+ ]
91
+
92
+ results: List[CommandResult] = []
93
+
94
+ for script_name, command in checks:
95
+ if script_name not in scripts:
96
+ results.append(
97
+ CommandResult(
98
+ name=script_name,
99
+ command=command,
100
+ skipped=True,
101
+ success=True,
102
+ reason=f"script '{script_name}' not found in package.json",
103
+ )
104
+ )
105
+ continue
106
+
107
+ results.append(self._run(script_name, command))
108
+
109
+ success = all(result.success for result in results)
110
+ return CommandValidationReport(success=success, results=results)
111
+
112
+ def _run(self, name: str, command: List[str]) -> CommandResult:
113
+ try:
114
+ completed = subprocess.run(
115
+ command,
116
+ cwd=self.project_path,
117
+ capture_output=True,
118
+ text=True,
119
+ timeout=60,
120
+ )
121
+
122
+ return CommandResult(
123
+ name=name,
124
+ command=command,
125
+ skipped=False,
126
+ success=completed.returncode == 0,
127
+ returncode=completed.returncode,
128
+ stdout=completed.stdout[-4000:],
129
+ stderr=completed.stderr[-4000:],
130
+ )
131
+ except FileNotFoundError:
132
+ return CommandResult(
133
+ name=name,
134
+ command=command,
135
+ skipped=True,
136
+ success=True,
137
+ reason=f"Command not available: {command[0]}",
138
+ )
139
+ except subprocess.TimeoutExpired:
140
+ return CommandResult(
141
+ name=name,
142
+ command=command,
143
+ skipped=False,
144
+ success=False,
145
+ reason="Command timed out after 60 seconds",
146
+ )
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: archapi
3
+ Version: 0.3.0
4
+ Summary: Architecture-preserving REST API synthesis library
5
+ Author: ArchAPI Research Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rohith5005/archapi
8
+ Project-URL: Repository, https://github.com/rohith5005/archapi
9
+ Project-URL: Issues, https://github.com/rohith5005/archapi/issues
10
+ Keywords: rest-api,code-generation,architecture,express,fastapi
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # ArchAPI
25
+
26
+ ArchAPI is a Python library for architecture-preserving REST API generation.
27
+
28
+ It scans an existing backend project, detects the framework, understands the project structure, plans a REST API, generates framework-specific files, validates the output, and writes files only when explicitly requested.
29
+
30
+ ---
31
+
32
+ ## Current Status
33
+
34
+ Current checkpoint: **Phase 3 complete**
35
+
36
+ Completed:
37
+
38
+ - Functional Python package
39
+ - Express TypeScript adapter
40
+ - FastAPI adapter
41
+ - Generic fallback adapter
42
+ - Framework detection
43
+ - Project scanning
44
+ - API architecture model
45
+ - Confidence scoring
46
+ - Low-confidence blocking
47
+ - Strict config mode
48
+ - REST intent planner
49
+ - Code generation
50
+ - Dry-run generation
51
+ - Safe apply
52
+ - Overwrite protection
53
+ - Cache and changed-file detection
54
+ - Secret scanner
55
+ - Context redaction
56
+ - Policy gate
57
+ - Architecture consistency score
58
+ - Command validation
59
+ - Unified regression test suite
60
+
61
+ ---
62
+
63
+ ## Supported Frameworks
64
+
65
+ Dedicated generation support currently exists for:
66
+
67
+ - Express TypeScript
68
+ - FastAPI
69
+
70
+ Other frameworks may be detected, but they currently use the generic fallback adapter.
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ From the project root:
77
+
78
+ ```bash
79
+ pip install -e .
@@ -0,0 +1,33 @@
1
+ archapi/__init__.py,sha256=7KPVSZPtBi98Z0mwpb7ew2pM30Wlkc3_sHG5cGG4BG8,56
2
+ archapi/core.py,sha256=mp08XxxSXAXp3KjAVL5NKmJ1RWjXInJJrbdAIR9bMbc,10164
3
+ archapi/types.py,sha256=vZPvvRgmu3IFl6hbUeQXhbvHzTeQNlQPQJd-tXckOxs,2884
4
+ archapi/frameworks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ archapi/frameworks/base.py,sha256=LpYW2n_p7SmO8NTEgN_K2cxR0fsG003gDHZD4XchYig,1382
6
+ archapi/frameworks/detector.py,sha256=4KcRi-2XwxJwvyNpYao3fci-mVyQoNZhBUMnnagzmvM,4056
7
+ archapi/frameworks/fastapi_adapter.py,sha256=vpYlYQKoKJRwB4M4WQ_JQB9I5E0Ix9CL1aqutCl_i_M,7002
8
+ archapi/frameworks/generic.py,sha256=87cZIXyrLQsQ4oPBF5DvwZlnQEGznwF5L0l0j3-iUJY,7173
9
+ archapi/frameworks/registry.py,sha256=TuWk4nX57hZ3bGcoOB1Pqsj2idjkZ7fVvWKRu9RGTqc,900
10
+ archapi/frameworks/express_ts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ archapi/frameworks/express_ts/adapter.py,sha256=WtqJaIwMXjc0xPh2NhlOGVLpKV1z0HhTwcy5oPNFlNI,8333
12
+ archapi/generation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ archapi/genome/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ archapi/indexing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ archapi/indexing/cache.py,sha256=JQ48ID5iDhtaMRZiUY7GjYxZl443uFh2oRSsrMmmIAE,3691
16
+ archapi/mapping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ archapi/planning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ archapi/planning/intent_planner.py,sha256=32BJc1wgilb15GNsI-qCM2oHQZ7Y_LB475rxF1WabG8,5226
19
+ archapi/planning/task_dag.py,sha256=qhs2gzLhyMoXzKHwYJKf93-f_5k55hM-jD9tH479HrE,2582
20
+ archapi/scanner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ archapi/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ archapi/security/context_redactor.py,sha256=aoWLBkbPaXwrJM-hp6oalWi2x_T5iRz3bKpaNZ8iQI8,1168
23
+ archapi/security/policy_gate.py,sha256=RL9-9BeOOKxlU_Fv8YOv5M3smFUb4Z1c6mx0O_JlAr4,1860
24
+ archapi/security/secret_scanner.py,sha256=BOk00PM-C71nsKDRAvj_Ncd6nugRYJ2pix5QYd-X-Ds,2623
25
+ archapi/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ archapi/validation/architecture_score.py,sha256=Blk_6pWtbuNR7q0GPAqLV-y9XitXZa93np8sn17bUdM,3633
27
+ archapi/validation/basic_validators.py,sha256=m5tTyaQ1P5kOkTWpBo0ejI0D8VjiPrBS-RdruBImD9A,1216
28
+ archapi/validation/command_validator.py,sha256=mLOS6VKjJaxxMhMgZGKVX0FGJi20iynNokFpNlDRx-k,4342
29
+ archapi-0.3.0.dist-info/licenses/LICENSE,sha256=r_ruhz8pLL0BO1NnEQBZ9YxDJP8u7N-i-EM4VAkVJok,1053
30
+ archapi-0.3.0.dist-info/METADATA,sha256=XY1MSBDwxrT0JmQ2ls73eHdXQCSmOUWs4AO4JNgbsAE,2095
31
+ archapi-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
32
+ archapi-0.3.0.dist-info/top_level.txt,sha256=UsoF2Svu8dT7Sq21TOAdv7tn7QeWZ_fYi4cNHGuPNRE,8
33
+ archapi-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rohith Chikkala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files, to deal in the Software
7
+ without restriction, including without limitation the rights to use, copy,
8
+ modify, merge, publish, distribute, sublicense, and/or sell copies of the
9
+ Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ archapi