archapi 0.3.0__tar.gz → 0.3.1__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 (39) hide show
  1. {archapi-0.3.0 → archapi-0.3.1}/PKG-INFO +1 -1
  2. {archapi-0.3.0 → archapi-0.3.1}/archapi.egg-info/PKG-INFO +1 -1
  3. {archapi-0.3.0 → archapi-0.3.1}/pyproject.toml +1 -1
  4. archapi-0.3.1/tests/test_archapi_suite.py +248 -0
  5. archapi-0.3.0/tests/test_archapi_suite.py +0 -118
  6. {archapi-0.3.0 → archapi-0.3.1}/LICENSE +0 -0
  7. {archapi-0.3.0 → archapi-0.3.1}/README.md +0 -0
  8. {archapi-0.3.0 → archapi-0.3.1}/archapi/__init__.py +0 -0
  9. {archapi-0.3.0 → archapi-0.3.1}/archapi/core.py +0 -0
  10. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/__init__.py +0 -0
  11. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/base.py +0 -0
  12. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/detector.py +0 -0
  13. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/express_ts/__init__.py +0 -0
  14. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/express_ts/adapter.py +0 -0
  15. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/fastapi_adapter.py +0 -0
  16. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/generic.py +0 -0
  17. {archapi-0.3.0 → archapi-0.3.1}/archapi/frameworks/registry.py +0 -0
  18. {archapi-0.3.0 → archapi-0.3.1}/archapi/generation/__init__.py +0 -0
  19. {archapi-0.3.0 → archapi-0.3.1}/archapi/genome/__init__.py +0 -0
  20. {archapi-0.3.0 → archapi-0.3.1}/archapi/indexing/__init__.py +0 -0
  21. {archapi-0.3.0 → archapi-0.3.1}/archapi/indexing/cache.py +0 -0
  22. {archapi-0.3.0 → archapi-0.3.1}/archapi/mapping/__init__.py +0 -0
  23. {archapi-0.3.0 → archapi-0.3.1}/archapi/planning/__init__.py +0 -0
  24. {archapi-0.3.0 → archapi-0.3.1}/archapi/planning/intent_planner.py +0 -0
  25. {archapi-0.3.0 → archapi-0.3.1}/archapi/planning/task_dag.py +0 -0
  26. {archapi-0.3.0 → archapi-0.3.1}/archapi/scanner/__init__.py +0 -0
  27. {archapi-0.3.0 → archapi-0.3.1}/archapi/security/__init__.py +0 -0
  28. {archapi-0.3.0 → archapi-0.3.1}/archapi/security/context_redactor.py +0 -0
  29. {archapi-0.3.0 → archapi-0.3.1}/archapi/security/policy_gate.py +0 -0
  30. {archapi-0.3.0 → archapi-0.3.1}/archapi/security/secret_scanner.py +0 -0
  31. {archapi-0.3.0 → archapi-0.3.1}/archapi/types.py +0 -0
  32. {archapi-0.3.0 → archapi-0.3.1}/archapi/validation/__init__.py +0 -0
  33. {archapi-0.3.0 → archapi-0.3.1}/archapi/validation/architecture_score.py +0 -0
  34. {archapi-0.3.0 → archapi-0.3.1}/archapi/validation/basic_validators.py +0 -0
  35. {archapi-0.3.0 → archapi-0.3.1}/archapi/validation/command_validator.py +0 -0
  36. {archapi-0.3.0 → archapi-0.3.1}/archapi.egg-info/SOURCES.txt +0 -0
  37. {archapi-0.3.0 → archapi-0.3.1}/archapi.egg-info/dependency_links.txt +0 -0
  38. {archapi-0.3.0 → archapi-0.3.1}/archapi.egg-info/top_level.txt +0 -0
  39. {archapi-0.3.0 → archapi-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archapi
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Architecture-preserving REST API synthesis library
5
5
  Author: ArchAPI Research Team
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archapi
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Architecture-preserving REST API synthesis library
5
5
  Author: ArchAPI Research Team
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "archapi"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Architecture-preserving REST API synthesis library"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,248 @@
1
+ import tempfile
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+ from archapi import ArchAPI
6
+
7
+
8
+ def create_express_project(root: Path) -> Path:
9
+ project = root / "express_basic"
10
+
11
+ (project / "src/routes").mkdir(parents=True, exist_ok=True)
12
+ (project / "src/controllers").mkdir(parents=True, exist_ok=True)
13
+ (project / "src/services").mkdir(parents=True, exist_ok=True)
14
+ (project / "src/schemas").mkdir(parents=True, exist_ok=True)
15
+ (project / "src/models").mkdir(parents=True, exist_ok=True)
16
+ (project / "src/middleware").mkdir(parents=True, exist_ok=True)
17
+ (project / "tests").mkdir(parents=True, exist_ok=True)
18
+
19
+ (project / "package.json").write_text(
20
+ '{"dependencies": {"express": "^4.18.0", "zod": "^3.0.0"}, '
21
+ '"devDependencies": {"jest": "^29.0.0", "supertest": "^6.0.0"}}'
22
+ )
23
+
24
+ (project / "src/routes/user.routes.ts").write_text(
25
+ 'import { Router } from "express";\n'
26
+ 'import { userController } from "../controllers/user.controller";\n\n'
27
+ 'const router = Router();\n'
28
+ 'router.get("/users/:id", userController.handle);\n'
29
+ 'export default router;\n'
30
+ )
31
+
32
+ (project / "src/controllers/user.controller.ts").write_text(
33
+ 'import { Request, Response, NextFunction } from "express";\n\n'
34
+ 'export const userController = {\n'
35
+ ' async handle(req: Request, res: Response, next: NextFunction) {\n'
36
+ ' return res.status(200).json({ data: {} });\n'
37
+ ' },\n'
38
+ '};\n'
39
+ )
40
+
41
+ (project / "src/services/user.service.ts").write_text(
42
+ 'export const userService = {\n'
43
+ ' async execute() {\n'
44
+ ' return {};\n'
45
+ ' },\n'
46
+ '};\n'
47
+ )
48
+
49
+ (project / "src/schemas/user.schema.ts").write_text(
50
+ 'import { z } from "zod";\n'
51
+ 'export const userRequestSchema = z.object({});\n'
52
+ )
53
+
54
+ (project / "src/models/user.model.ts").write_text(
55
+ 'export interface User { id: string; }\n'
56
+ )
57
+
58
+ (project / "src/middleware/auth.middleware.ts").write_text(
59
+ 'export function requireAuth(req: unknown, res: unknown, next: () => void) {\n'
60
+ ' next();\n'
61
+ '}\n'
62
+ )
63
+
64
+ (project / "tests/user.test.ts").write_text(
65
+ 'describe("user", () => {\n'
66
+ ' it("works", () => {\n'
67
+ ' expect(true).toBe(true);\n'
68
+ ' });\n'
69
+ '});\n'
70
+ )
71
+
72
+ return project
73
+
74
+
75
+ def create_fastapi_project(root: Path) -> Path:
76
+ project = root / "fastapi_basic"
77
+
78
+ (project / "app/routers").mkdir(parents=True, exist_ok=True)
79
+ (project / "app/services").mkdir(parents=True, exist_ok=True)
80
+ (project / "app/schemas").mkdir(parents=True, exist_ok=True)
81
+ (project / "tests").mkdir(parents=True, exist_ok=True)
82
+
83
+ (project / "requirements.txt").write_text("fastapi\npydantic\npytest\n")
84
+
85
+ (project / "app/__init__.py").write_text("")
86
+ (project / "app/routers/__init__.py").write_text("")
87
+ (project / "app/services/__init__.py").write_text("")
88
+ (project / "app/schemas/__init__.py").write_text("")
89
+
90
+ (project / "app/routers/user_router.py").write_text(
91
+ "from fastapi import APIRouter\n\n"
92
+ "router = APIRouter()\n\n"
93
+ '@router.get("/users/{id}")\n'
94
+ "async def get_user(id: str):\n"
95
+ " return {'id': id}\n"
96
+ )
97
+
98
+ (project / "app/services/user_service.py").write_text(
99
+ "class UserService:\n"
100
+ " async def execute(self):\n"
101
+ " return {}\n\n"
102
+ "user_service = UserService()\n"
103
+ )
104
+
105
+ (project / "app/schemas/user_schema.py").write_text(
106
+ "from pydantic import BaseModel\n\n"
107
+ "class UserResponse(BaseModel):\n"
108
+ " id: str\n"
109
+ )
110
+
111
+ (project / "tests/test_user.py").write_text(
112
+ "def test_user_placeholder():\n"
113
+ " assert True\n"
114
+ )
115
+
116
+ return project
117
+
118
+
119
+ class TestExpressGeneration(unittest.TestCase):
120
+ def test_express_detection_and_generation(self):
121
+ with tempfile.TemporaryDirectory() as tmp:
122
+ project = create_express_project(Path(tmp))
123
+ engine = ArchAPI(str(project))
124
+
125
+ detection = engine.detect_framework()
126
+ result = engine.generate_api("Create POST API for product review", dry_run=True)
127
+ score = engine.score_architecture(result)
128
+
129
+ self.assertEqual(detection.framework, "express-typescript")
130
+ self.assertTrue(result.validation_report.success, result.validation_report.errors)
131
+ self.assertEqual(result.plan.method, "POST")
132
+ self.assertEqual(result.plan.path, "/products/{product_id}/reviews")
133
+ self.assertEqual(len(result.files), 5)
134
+ self.assertEqual(score.percentage, 100.0)
135
+ self.assertIn('router.post("/products/:productId/reviews"', result.files[0].content)
136
+
137
+
138
+ class TestFastAPIGeneration(unittest.TestCase):
139
+ def test_fastapi_detection_and_generation(self):
140
+ with tempfile.TemporaryDirectory() as tmp:
141
+ project = create_fastapi_project(Path(tmp))
142
+ engine = ArchAPI(str(project))
143
+
144
+ detection = engine.detect_framework()
145
+ result = engine.generate_api("Create GET API for payment status", dry_run=True)
146
+ score = engine.score_architecture(result)
147
+
148
+ self.assertEqual(detection.framework, "fastapi")
149
+ self.assertTrue(result.validation_report.success, result.validation_report.errors)
150
+ self.assertEqual(result.plan.method, "GET")
151
+ self.assertEqual(result.plan.path, "/payments/{id}/status")
152
+ self.assertEqual(len(result.files), 4)
153
+ self.assertEqual(score.percentage, 100.0)
154
+ self.assertIn('@router.get("/payments/{id}/status"', result.files[0].content)
155
+
156
+
157
+ class TestStrictConfigMode(unittest.TestCase):
158
+ def test_express_strict_config(self):
159
+ with tempfile.TemporaryDirectory() as tmp:
160
+ project = create_express_project(Path(tmp))
161
+
162
+ engine = ArchAPI(
163
+ str(project.parent),
164
+ framework="express-typescript",
165
+ config={
166
+ "route_dir": f"{project.name}/src/routes",
167
+ "controller_dir": f"{project.name}/src/controllers",
168
+ "service_dir": f"{project.name}/src/services",
169
+ "model_dir": f"{project.name}/src/models",
170
+ "schema_dir": f"{project.name}/src/schemas",
171
+ "middleware_dir": f"{project.name}/src/middleware",
172
+ "test_dir": f"{project.name}/tests",
173
+ },
174
+ )
175
+
176
+ scan = engine.scan()
177
+ result = engine.generate_api("Create POST API for product review", dry_run=True)
178
+
179
+ self.assertEqual(len(scan.unknown), 0)
180
+ self.assertTrue(result.validation_report.success, result.validation_report.errors)
181
+
182
+ def test_fastapi_strict_config(self):
183
+ with tempfile.TemporaryDirectory() as tmp:
184
+ project = create_fastapi_project(Path(tmp))
185
+
186
+ engine = ArchAPI(
187
+ str(project.parent),
188
+ framework="fastapi",
189
+ config={
190
+ "route_dir": f"{project.name}/app/routers",
191
+ "service_dir": f"{project.name}/app/services",
192
+ "schema_dir": f"{project.name}/app/schemas",
193
+ "test_dir": f"{project.name}/tests",
194
+ },
195
+ )
196
+
197
+ scan = engine.scan()
198
+ result = engine.generate_api("Create POST API for product review", dry_run=True)
199
+
200
+ self.assertEqual(len(scan.unknown), 0)
201
+ self.assertTrue(result.validation_report.success, result.validation_report.errors)
202
+
203
+
204
+ class TestSafetyAndUtilities(unittest.TestCase):
205
+ def test_low_confidence_project_is_blocked(self):
206
+ with tempfile.TemporaryDirectory() as tmp:
207
+ empty_project = Path(tmp) / "empty_project"
208
+ empty_project.mkdir()
209
+
210
+ engine = ArchAPI(str(empty_project))
211
+ plan = engine.plan_api("Create GET API for user order history")
212
+ result = engine.generate_api("Create GET API for user order history", dry_run=True)
213
+
214
+ self.assertFalse(plan.generation_allowed)
215
+ self.assertEqual(result.files, [])
216
+ self.assertFalse(result.validation_report.success)
217
+ self.assertTrue(
218
+ "Framework could not be confidently detected" in plan.reason
219
+ or "Architecture confidence too low" in plan.reason
220
+ )
221
+
222
+ def test_redaction(self):
223
+ with tempfile.TemporaryDirectory() as tmp:
224
+ project = create_express_project(Path(tmp))
225
+ engine = ArchAPI(str(project))
226
+
227
+ redacted = engine.redact_context(
228
+ 'API_KEY="1234567890abcdef" TOKEN="abcdef1234567890" SECRET="abcdef1234567890"'
229
+ )
230
+
231
+ self.assertIn("[REDACTED_API_KEY]", redacted)
232
+ self.assertIn("[REDACTED_TOKEN]", redacted)
233
+ self.assertIn("[REDACTED_SECRET]", redacted)
234
+
235
+ def test_policy_validation(self):
236
+ with tempfile.TemporaryDirectory() as tmp:
237
+ project = create_express_project(Path(tmp))
238
+ engine = ArchAPI(str(project))
239
+
240
+ result = engine.generate_api("Create GET API for payment status", dry_run=True)
241
+ policy = engine.validate_policy(result)
242
+
243
+ self.assertTrue(policy.allowed)
244
+ self.assertEqual(policy.errors, [])
245
+
246
+
247
+ if __name__ == "__main__":
248
+ unittest.main()
@@ -1,118 +0,0 @@
1
- import unittest
2
-
3
- from archapi import ArchAPI
4
-
5
-
6
- class TestExpressGeneration(unittest.TestCase):
7
- def test_express_detection_and_generation(self):
8
- engine = ArchAPI("./sample_projects/express_basic")
9
-
10
- detection = engine.detect_framework()
11
- result = engine.generate_api("Create POST API for product review", dry_run=True)
12
- score = engine.score_architecture(result)
13
-
14
- self.assertEqual(detection.framework, "express-typescript")
15
- self.assertTrue(result.validation_report.success)
16
- self.assertEqual(result.plan.method, "POST")
17
- self.assertEqual(result.plan.path, "/products/{product_id}/reviews")
18
- self.assertEqual(len(result.files), 5)
19
- self.assertEqual(score.percentage, 100.0)
20
- self.assertIn('router.post("/products/:productId/reviews"', result.files[0].content)
21
-
22
-
23
- class TestFastAPIGeneration(unittest.TestCase):
24
- def test_fastapi_detection_and_generation(self):
25
- engine = ArchAPI("./sample_projects/fastapi_basic")
26
-
27
- detection = engine.detect_framework()
28
- result = engine.generate_api("Create GET API for payment status", dry_run=True)
29
- score = engine.score_architecture(result)
30
-
31
- self.assertEqual(detection.framework, "fastapi")
32
- self.assertTrue(result.validation_report.success)
33
- self.assertEqual(result.plan.method, "GET")
34
- self.assertEqual(result.plan.path, "/payments/{id}/status")
35
- self.assertEqual(len(result.files), 4)
36
- self.assertEqual(score.percentage, 100.0)
37
- self.assertIn('@router.get("/payments/{id}/status"', result.files[0].content)
38
-
39
-
40
- class TestStrictConfigMode(unittest.TestCase):
41
- def test_express_strict_config(self):
42
- engine = ArchAPI(
43
- ".",
44
- framework="express-typescript",
45
- config={
46
- "route_dir": "sample_projects/express_basic/src/routes",
47
- "controller_dir": "sample_projects/express_basic/src/controllers",
48
- "service_dir": "sample_projects/express_basic/src/services",
49
- "model_dir": "sample_projects/express_basic/src/models",
50
- "schema_dir": "sample_projects/express_basic/src/schemas",
51
- "middleware_dir": "sample_projects/express_basic/src/middleware",
52
- "test_dir": "sample_projects/express_basic/tests",
53
- },
54
- )
55
-
56
- scan = engine.scan()
57
- result = engine.generate_api("Create POST API for product review", dry_run=True)
58
-
59
- self.assertEqual(len(scan.unknown), 0)
60
- self.assertTrue(result.validation_report.success)
61
-
62
- def test_fastapi_strict_config(self):
63
- engine = ArchAPI(
64
- ".",
65
- framework="fastapi",
66
- config={
67
- "route_dir": "sample_projects/fastapi_basic/app/routers",
68
- "service_dir": "sample_projects/fastapi_basic/app/services",
69
- "schema_dir": "sample_projects/fastapi_basic/app/schemas",
70
- "test_dir": "sample_projects/fastapi_basic/tests",
71
- },
72
- )
73
-
74
- scan = engine.scan()
75
- result = engine.generate_api("Create POST API for product review", dry_run=True)
76
-
77
- self.assertEqual(len(scan.unknown), 0)
78
- self.assertTrue(result.validation_report.success)
79
-
80
-
81
- class TestSafetyAndUtilities(unittest.TestCase):
82
- def test_low_confidence_project_is_blocked(self):
83
- engine = ArchAPI(".")
84
-
85
- plan = engine.plan_api("Create GET API for user order history")
86
- result = engine.generate_api("Create GET API for user order history", dry_run=True)
87
-
88
- self.assertFalse(plan.generation_allowed)
89
- self.assertEqual(result.files, [])
90
- self.assertFalse(result.validation_report.success)
91
- self.assertTrue(
92
- "Framework could not be confidently detected" in plan.reason
93
- or "Architecture confidence too low" in plan.reason
94
- )
95
-
96
- def test_redaction(self):
97
- engine = ArchAPI("./sample_projects/express_basic")
98
-
99
- redacted = engine.redact_context(
100
- 'API_KEY="1234567890abcdef" TOKEN="abcdef1234567890" SECRET="abcdef1234567890"'
101
- )
102
-
103
- self.assertIn("[REDACTED_API_KEY]", redacted)
104
- self.assertIn("[REDACTED_TOKEN]", redacted)
105
- self.assertIn("[REDACTED_SECRET]", redacted)
106
-
107
- def test_policy_validation(self):
108
- engine = ArchAPI("./sample_projects/express_basic")
109
-
110
- result = engine.generate_api("Create GET API for payment status", dry_run=True)
111
- policy = engine.validate_policy(result)
112
-
113
- self.assertTrue(policy.allowed)
114
- self.assertEqual(policy.errors, [])
115
-
116
-
117
- if __name__ == "__main__":
118
- unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes