nc1709 1.15.4__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 (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. nc1709-1.15.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,887 @@
1
+ """
2
+ FastAPI Agent for NC1709
3
+ Scaffolds FastAPI projects, endpoints, models, and more
4
+ """
5
+ import os
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional, List
9
+ from dataclasses import dataclass
10
+
11
+ from ..base import (
12
+ Plugin, PluginMetadata, PluginCapability,
13
+ ActionResult
14
+ )
15
+
16
+
17
+ @dataclass
18
+ class EndpointInfo:
19
+ """Represents an API endpoint"""
20
+ method: str
21
+ path: str
22
+ function_name: str
23
+ description: str
24
+ request_model: Optional[str] = None
25
+ response_model: Optional[str] = None
26
+
27
+
28
+ class FastAPIAgent(Plugin):
29
+ """
30
+ FastAPI scaffolding and development agent.
31
+
32
+ Provides:
33
+ - Project scaffolding with best practices
34
+ - Endpoint generation
35
+ - Pydantic model generation
36
+ - CRUD operations scaffolding
37
+ - Database integration (SQLAlchemy)
38
+ - Authentication scaffolding
39
+ """
40
+
41
+ METADATA = PluginMetadata(
42
+ name="fastapi",
43
+ version="1.0.0",
44
+ description="FastAPI project scaffolding and development",
45
+ author="NC1709 Team",
46
+ capabilities=[
47
+ PluginCapability.CODE_GENERATION,
48
+ PluginCapability.PROJECT_SCAFFOLDING
49
+ ],
50
+ keywords=[
51
+ "fastapi", "api", "rest", "endpoint", "pydantic",
52
+ "model", "schema", "crud", "router", "uvicorn",
53
+ "async", "python", "backend"
54
+ ],
55
+ config_schema={
56
+ "project_path": {"type": "string", "default": "."},
57
+ "use_async": {"type": "boolean", "default": True},
58
+ "database": {"type": "string", "enum": ["sqlite", "postgresql", "mysql", "none"], "default": "sqlite"}
59
+ }
60
+ )
61
+
62
+ @property
63
+ def metadata(self) -> PluginMetadata:
64
+ return self.METADATA
65
+
66
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
67
+ super().__init__(config)
68
+ self._project_path: Optional[Path] = None
69
+
70
+ def initialize(self) -> bool:
71
+ """Initialize the FastAPI agent"""
72
+ self._project_path = Path(self._config.get("project_path", ".")).resolve()
73
+ return True
74
+
75
+ def cleanup(self) -> None:
76
+ """Cleanup resources"""
77
+ pass
78
+
79
+ def _register_actions(self) -> None:
80
+ """Register FastAPI actions"""
81
+ self.register_action(
82
+ "scaffold",
83
+ self.scaffold_project,
84
+ "Create a new FastAPI project structure",
85
+ parameters={
86
+ "name": {"type": "string", "required": True},
87
+ "with_db": {"type": "boolean", "default": True},
88
+ "with_auth": {"type": "boolean", "default": False}
89
+ }
90
+ )
91
+
92
+ self.register_action(
93
+ "endpoint",
94
+ self.create_endpoint,
95
+ "Generate a new API endpoint",
96
+ parameters={
97
+ "path": {"type": "string", "required": True},
98
+ "method": {"type": "string", "default": "GET"},
99
+ "name": {"type": "string", "required": True}
100
+ }
101
+ )
102
+
103
+ self.register_action(
104
+ "model",
105
+ self.create_model,
106
+ "Generate a Pydantic model",
107
+ parameters={
108
+ "name": {"type": "string", "required": True},
109
+ "fields": {"type": "object", "required": True}
110
+ }
111
+ )
112
+
113
+ self.register_action(
114
+ "crud",
115
+ self.create_crud,
116
+ "Generate CRUD endpoints for a model",
117
+ parameters={
118
+ "model": {"type": "string", "required": True},
119
+ "prefix": {"type": "string", "default": ""}
120
+ }
121
+ )
122
+
123
+ self.register_action(
124
+ "router",
125
+ self.create_router,
126
+ "Generate a new router module",
127
+ parameters={
128
+ "name": {"type": "string", "required": True},
129
+ "prefix": {"type": "string", "default": ""}
130
+ }
131
+ )
132
+
133
+ self.register_action(
134
+ "analyze",
135
+ self.analyze_project,
136
+ "Analyze existing FastAPI project structure"
137
+ )
138
+
139
+ def scaffold_project(
140
+ self,
141
+ name: str,
142
+ with_db: bool = True,
143
+ with_auth: bool = False
144
+ ) -> ActionResult:
145
+ """Create a new FastAPI project structure
146
+
147
+ Args:
148
+ name: Project name
149
+ with_db: Include database setup
150
+ with_auth: Include authentication
151
+
152
+ Returns:
153
+ ActionResult
154
+ """
155
+ project_dir = self._project_path / name
156
+
157
+ if project_dir.exists():
158
+ return ActionResult.fail(f"Directory '{name}' already exists")
159
+
160
+ try:
161
+ # Create directory structure
162
+ dirs = [
163
+ project_dir,
164
+ project_dir / "app",
165
+ project_dir / "app" / "api",
166
+ project_dir / "app" / "api" / "v1",
167
+ project_dir / "app" / "core",
168
+ project_dir / "app" / "models",
169
+ project_dir / "app" / "schemas",
170
+ project_dir / "app" / "services",
171
+ project_dir / "tests",
172
+ ]
173
+
174
+ if with_db:
175
+ dirs.append(project_dir / "app" / "db")
176
+ dirs.append(project_dir / "alembic")
177
+
178
+ for d in dirs:
179
+ d.mkdir(parents=True, exist_ok=True)
180
+
181
+ # Create main.py
182
+ main_content = self._generate_main(name, with_db, with_auth)
183
+ (project_dir / "app" / "main.py").write_text(main_content)
184
+
185
+ # Create __init__.py files
186
+ for d in dirs:
187
+ if "app" in str(d) or "tests" in str(d):
188
+ init_file = d / "__init__.py"
189
+ if not init_file.exists():
190
+ init_file.write_text("")
191
+
192
+ # Create config
193
+ config_content = self._generate_config(name, with_db)
194
+ (project_dir / "app" / "core" / "config.py").write_text(config_content)
195
+
196
+ # Create requirements.txt
197
+ requirements = self._generate_requirements(with_db, with_auth)
198
+ (project_dir / "requirements.txt").write_text(requirements)
199
+
200
+ # Create .env.example
201
+ env_content = self._generate_env_example(name, with_db)
202
+ (project_dir / ".env.example").write_text(env_content)
203
+
204
+ # Create example router
205
+ router_content = self._generate_example_router()
206
+ (project_dir / "app" / "api" / "v1" / "health.py").write_text(router_content)
207
+
208
+ # Create API init
209
+ api_init = self._generate_api_init()
210
+ (project_dir / "app" / "api" / "v1" / "__init__.py").write_text(api_init)
211
+
212
+ if with_db:
213
+ # Create database setup
214
+ db_content = self._generate_db_setup()
215
+ (project_dir / "app" / "db" / "database.py").write_text(db_content)
216
+
217
+ # Create base model
218
+ base_model = self._generate_base_model()
219
+ (project_dir / "app" / "models" / "base.py").write_text(base_model)
220
+
221
+ if with_auth:
222
+ # Create auth module
223
+ auth_content = self._generate_auth_module()
224
+ (project_dir / "app" / "core" / "auth.py").write_text(auth_content)
225
+
226
+ files_created = len(list(project_dir.rglob("*")))
227
+
228
+ return ActionResult.ok(
229
+ message=f"Created FastAPI project '{name}' with {files_created} files",
230
+ data={
231
+ "project_path": str(project_dir),
232
+ "with_db": with_db,
233
+ "with_auth": with_auth,
234
+ "next_steps": [
235
+ f"cd {name}",
236
+ "python -m venv venv",
237
+ "source venv/bin/activate",
238
+ "pip install -r requirements.txt",
239
+ "uvicorn app.main:app --reload"
240
+ ]
241
+ }
242
+ )
243
+
244
+ except Exception as e:
245
+ return ActionResult.fail(str(e))
246
+
247
+ def create_endpoint(
248
+ self,
249
+ path: str,
250
+ method: str = "GET",
251
+ name: str = ""
252
+ ) -> ActionResult:
253
+ """Generate a new API endpoint
254
+
255
+ Args:
256
+ path: URL path (e.g., "/users/{user_id}")
257
+ method: HTTP method
258
+ name: Function name
259
+
260
+ Returns:
261
+ ActionResult with generated code
262
+ """
263
+ method = method.upper()
264
+ if method not in ["GET", "POST", "PUT", "DELETE", "PATCH"]:
265
+ return ActionResult.fail(f"Invalid HTTP method: {method}")
266
+
267
+ # Generate function name if not provided
268
+ if not name:
269
+ name = self._path_to_function_name(path, method)
270
+
271
+ # Detect path parameters
272
+ path_params = re.findall(r'\{(\w+)\}', path)
273
+
274
+ # Generate endpoint code
275
+ code = self._generate_endpoint_code(path, method, name, path_params)
276
+
277
+ return ActionResult.ok(
278
+ message=f"Generated {method} {path} endpoint",
279
+ data={
280
+ "code": code,
281
+ "function_name": name,
282
+ "path_params": path_params
283
+ }
284
+ )
285
+
286
+ def create_model(
287
+ self,
288
+ name: str,
289
+ fields: Dict[str, str]
290
+ ) -> ActionResult:
291
+ """Generate a Pydantic model
292
+
293
+ Args:
294
+ name: Model name
295
+ fields: Dict of field_name -> field_type
296
+
297
+ Returns:
298
+ ActionResult with generated code
299
+ """
300
+ # Generate model code
301
+ code = self._generate_pydantic_model(name, fields)
302
+
303
+ return ActionResult.ok(
304
+ message=f"Generated Pydantic model '{name}'",
305
+ data={"code": code, "fields": list(fields.keys())}
306
+ )
307
+
308
+ def create_crud(
309
+ self,
310
+ model: str,
311
+ prefix: str = ""
312
+ ) -> ActionResult:
313
+ """Generate CRUD endpoints for a model
314
+
315
+ Args:
316
+ model: Model name
317
+ prefix: URL prefix
318
+
319
+ Returns:
320
+ ActionResult with generated code
321
+ """
322
+ model_lower = model.lower()
323
+ prefix = prefix or f"/{model_lower}s"
324
+
325
+ code = self._generate_crud_router(model, prefix)
326
+
327
+ return ActionResult.ok(
328
+ message=f"Generated CRUD router for '{model}'",
329
+ data={
330
+ "code": code,
331
+ "endpoints": [
332
+ f"GET {prefix}",
333
+ f"POST {prefix}",
334
+ f"GET {prefix}/{{id}}",
335
+ f"PUT {prefix}/{{id}}",
336
+ f"DELETE {prefix}/{{id}}"
337
+ ]
338
+ }
339
+ )
340
+
341
+ def create_router(
342
+ self,
343
+ name: str,
344
+ prefix: str = ""
345
+ ) -> ActionResult:
346
+ """Generate a new router module
347
+
348
+ Args:
349
+ name: Router name
350
+ prefix: URL prefix
351
+
352
+ Returns:
353
+ ActionResult with generated code
354
+ """
355
+ prefix = prefix or f"/{name}"
356
+
357
+ code = f'''"""
358
+ {name.title()} Router
359
+ """
360
+ from fastapi import APIRouter, Depends, HTTPException, status
361
+
362
+ router = APIRouter(prefix="{prefix}", tags=["{name}"])
363
+
364
+
365
+ @router.get("/")
366
+ async def list_{name}():
367
+ """List all {name}"""
368
+ return {{"message": "List {name}"}}
369
+
370
+
371
+ @router.get("/{{item_id}}")
372
+ async def get_{name}(item_id: int):
373
+ """Get a single {name}"""
374
+ return {{"id": item_id}}
375
+
376
+
377
+ @router.post("/", status_code=status.HTTP_201_CREATED)
378
+ async def create_{name}():
379
+ """Create a new {name}"""
380
+ return {{"message": "Created"}}
381
+
382
+
383
+ @router.put("/{{item_id}}")
384
+ async def update_{name}(item_id: int):
385
+ """Update a {name}"""
386
+ return {{"id": item_id, "updated": True}}
387
+
388
+
389
+ @router.delete("/{{item_id}}", status_code=status.HTTP_204_NO_CONTENT)
390
+ async def delete_{name}(item_id: int):
391
+ """Delete a {name}"""
392
+ return None
393
+ '''
394
+
395
+ return ActionResult.ok(
396
+ message=f"Generated router '{name}'",
397
+ data={"code": code, "prefix": prefix}
398
+ )
399
+
400
+ def analyze_project(self) -> ActionResult:
401
+ """Analyze existing FastAPI project structure
402
+
403
+ Returns:
404
+ ActionResult with project analysis
405
+ """
406
+ # Look for FastAPI indicators
407
+ app_dir = self._project_path / "app"
408
+ main_file = self._project_path / "main.py"
409
+ app_main = app_dir / "main.py" if app_dir.exists() else None
410
+
411
+ if not (main_file.exists() or (app_main and app_main.exists())):
412
+ return ActionResult.fail("No FastAPI project found in current directory")
413
+
414
+ analysis = {
415
+ "project_path": str(self._project_path),
416
+ "structure": [],
417
+ "routers": [],
418
+ "models": [],
419
+ "endpoints": []
420
+ }
421
+
422
+ # Scan for Python files
423
+ for py_file in self._project_path.rglob("*.py"):
424
+ rel_path = py_file.relative_to(self._project_path)
425
+ analysis["structure"].append(str(rel_path))
426
+
427
+ # Check for routers
428
+ content = py_file.read_text()
429
+ if "APIRouter" in content:
430
+ analysis["routers"].append(str(rel_path))
431
+
432
+ # Check for models
433
+ if "BaseModel" in content and "pydantic" in content.lower():
434
+ analysis["models"].append(str(rel_path))
435
+
436
+ # Find endpoints
437
+ for match in re.finditer(r'@\w+\.(get|post|put|delete|patch)\(["\']([^"\']+)', content):
438
+ analysis["endpoints"].append({
439
+ "file": str(rel_path),
440
+ "method": match.group(1).upper(),
441
+ "path": match.group(2)
442
+ })
443
+
444
+ return ActionResult.ok(
445
+ message=f"Analyzed project with {len(analysis['endpoints'])} endpoints",
446
+ data=analysis
447
+ )
448
+
449
+ # Code generation helpers
450
+
451
+ def _generate_main(self, name: str, with_db: bool, with_auth: bool) -> str:
452
+ """Generate main.py content"""
453
+ imports = ['from fastapi import FastAPI', 'from fastapi.middleware.cors import CORSMiddleware']
454
+
455
+ if with_db:
456
+ imports.append('from app.db.database import engine, Base')
457
+
458
+ code = '\n'.join(imports) + '\n'
459
+ code += f'''from app.api.v1 import health
460
+
461
+ app = FastAPI(
462
+ title="{name.replace('_', ' ').title()} API",
463
+ description="API built with FastAPI",
464
+ version="1.0.0"
465
+ )
466
+
467
+ # CORS middleware
468
+ app.add_middleware(
469
+ CORSMiddleware,
470
+ allow_origins=["*"],
471
+ allow_credentials=True,
472
+ allow_methods=["*"],
473
+ allow_headers=["*"],
474
+ )
475
+
476
+ # Include routers
477
+ app.include_router(health.router, prefix="/api/v1")
478
+ '''
479
+
480
+ if with_db:
481
+ code += '''
482
+
483
+ # Create database tables
484
+ @app.on_event("startup")
485
+ async def startup():
486
+ Base.metadata.create_all(bind=engine)
487
+ '''
488
+
489
+ code += '''
490
+
491
+ @app.get("/")
492
+ async def root():
493
+ return {"message": "Welcome to the API", "docs": "/docs"}
494
+ '''
495
+ return code
496
+
497
+ def _generate_config(self, name: str, with_db: bool) -> str:
498
+ """Generate config.py content"""
499
+ code = '''"""
500
+ Application configuration
501
+ """
502
+ from pydantic_settings import BaseSettings
503
+ from functools import lru_cache
504
+
505
+
506
+ class Settings(BaseSettings):
507
+ """Application settings"""
508
+ app_name: str = "''' + name + '''"
509
+ debug: bool = False
510
+ api_v1_prefix: str = "/api/v1"
511
+ '''
512
+ if with_db:
513
+ code += ''' database_url: str = "sqlite:///./app.db"
514
+ '''
515
+ code += '''
516
+ class Config:
517
+ env_file = ".env"
518
+
519
+
520
+ @lru_cache()
521
+ def get_settings() -> Settings:
522
+ return Settings()
523
+ '''
524
+ return code
525
+
526
+ def _generate_requirements(self, with_db: bool, with_auth: bool) -> str:
527
+ """Generate requirements.txt"""
528
+ reqs = [
529
+ "fastapi>=0.100.0",
530
+ "uvicorn[standard]>=0.23.0",
531
+ "pydantic>=2.0.0",
532
+ "pydantic-settings>=2.0.0",
533
+ "python-dotenv>=1.0.0",
534
+ ]
535
+
536
+ if with_db:
537
+ reqs.extend([
538
+ "sqlalchemy>=2.0.0",
539
+ "alembic>=1.12.0",
540
+ ])
541
+
542
+ if with_auth:
543
+ reqs.extend([
544
+ "python-jose[cryptography]>=3.3.0",
545
+ "passlib[bcrypt]>=1.7.0",
546
+ "python-multipart>=0.0.6",
547
+ ])
548
+
549
+ reqs.append("pytest>=7.0.0")
550
+ reqs.append("httpx>=0.24.0")
551
+
552
+ return '\n'.join(reqs) + '\n'
553
+
554
+ def _generate_env_example(self, name: str, with_db: bool) -> str:
555
+ """Generate .env.example"""
556
+ content = f'''# {name} Environment Variables
557
+ DEBUG=true
558
+ '''
559
+ if with_db:
560
+ content += 'DATABASE_URL=sqlite:///./app.db\n'
561
+ return content
562
+
563
+ def _generate_example_router(self) -> str:
564
+ """Generate example health router"""
565
+ return '''"""
566
+ Health check router
567
+ """
568
+ from fastapi import APIRouter
569
+
570
+ router = APIRouter(prefix="/health", tags=["health"])
571
+
572
+
573
+ @router.get("/")
574
+ async def health_check():
575
+ """Health check endpoint"""
576
+ return {"status": "healthy"}
577
+
578
+
579
+ @router.get("/ready")
580
+ async def readiness_check():
581
+ """Readiness check endpoint"""
582
+ return {"status": "ready"}
583
+ '''
584
+
585
+ def _generate_api_init(self) -> str:
586
+ """Generate API __init__.py"""
587
+ return '''"""
588
+ API v1 routers
589
+ """
590
+ from . import health
591
+
592
+ __all__ = ["health"]
593
+ '''
594
+
595
+ def _generate_db_setup(self) -> str:
596
+ """Generate database setup"""
597
+ return '''"""
598
+ Database configuration
599
+ """
600
+ from sqlalchemy import create_engine
601
+ from sqlalchemy.orm import sessionmaker, declarative_base
602
+ from app.core.config import get_settings
603
+
604
+ settings = get_settings()
605
+
606
+ engine = create_engine(
607
+ settings.database_url,
608
+ connect_args={"check_same_thread": False} # SQLite only
609
+ )
610
+
611
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
612
+
613
+ Base = declarative_base()
614
+
615
+
616
+ def get_db():
617
+ """Dependency for database session"""
618
+ db = SessionLocal()
619
+ try:
620
+ yield db
621
+ finally:
622
+ db.close()
623
+ '''
624
+
625
+ def _generate_base_model(self) -> str:
626
+ """Generate base SQLAlchemy model"""
627
+ return '''"""
628
+ Base model with common fields
629
+ """
630
+ from datetime import datetime
631
+ from sqlalchemy import Column, Integer, DateTime
632
+ from app.db.database import Base
633
+
634
+
635
+ class BaseModel(Base):
636
+ """Abstract base model with common fields"""
637
+ __abstract__ = True
638
+
639
+ id = Column(Integer, primary_key=True, index=True)
640
+ created_at = Column(DateTime, default=datetime.utcnow)
641
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
642
+ '''
643
+
644
+ def _generate_auth_module(self) -> str:
645
+ """Generate authentication module"""
646
+ return '''"""
647
+ Authentication utilities
648
+ """
649
+ from datetime import datetime, timedelta
650
+ from typing import Optional
651
+ from jose import JWTError, jwt
652
+ from passlib.context import CryptContext
653
+ from fastapi import Depends, HTTPException, status
654
+ from fastapi.security import OAuth2PasswordBearer
655
+
656
+ # Configuration
657
+ SECRET_KEY = "your-secret-key-change-in-production"
658
+ ALGORITHM = "HS256"
659
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
660
+
661
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
662
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
663
+
664
+
665
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
666
+ """Verify a password against its hash"""
667
+ return pwd_context.verify(plain_password, hashed_password)
668
+
669
+
670
+ def get_password_hash(password: str) -> str:
671
+ """Hash a password"""
672
+ return pwd_context.hash(password)
673
+
674
+
675
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
676
+ """Create a JWT access token"""
677
+ to_encode = data.copy()
678
+ expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
679
+ to_encode.update({"exp": expire})
680
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
681
+
682
+
683
+ async def get_current_user(token: str = Depends(oauth2_scheme)):
684
+ """Get current user from JWT token"""
685
+ credentials_exception = HTTPException(
686
+ status_code=status.HTTP_401_UNAUTHORIZED,
687
+ detail="Could not validate credentials",
688
+ headers={"WWW-Authenticate": "Bearer"},
689
+ )
690
+ try:
691
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
692
+ username: str = payload.get("sub")
693
+ if username is None:
694
+ raise credentials_exception
695
+ except JWTError:
696
+ raise credentials_exception
697
+ return {"username": username}
698
+ '''
699
+
700
+ def _path_to_function_name(self, path: str, method: str) -> str:
701
+ """Convert URL path to function name"""
702
+ # Remove path parameters
703
+ clean_path = re.sub(r'\{[^}]+\}', '', path)
704
+ # Convert to snake_case
705
+ name = clean_path.strip('/').replace('/', '_').replace('-', '_')
706
+ prefix = method.lower()
707
+ return f"{prefix}_{name}" if name else prefix
708
+
709
+ def _generate_endpoint_code(
710
+ self,
711
+ path: str,
712
+ method: str,
713
+ name: str,
714
+ path_params: List[str]
715
+ ) -> str:
716
+ """Generate endpoint code"""
717
+ decorator = f'@router.{method.lower()}("{path}")'
718
+
719
+ # Build function signature
720
+ params = [f"{p}: int" for p in path_params] # Default to int
721
+ params_str = ", ".join(params) if params else ""
722
+
723
+ if method in ["POST", "PUT", "PATCH"]:
724
+ # Add request body parameter
725
+ if params_str:
726
+ params_str += ", "
727
+ params_str += "data: dict"
728
+
729
+ code = f'''{decorator}
730
+ async def {name}({params_str}):
731
+ """
732
+ {method} {path}
733
+ """
734
+ '''
735
+ if method == "GET":
736
+ if path_params:
737
+ code += f' return {{"id": {path_params[0]}}}\n'
738
+ else:
739
+ code += ' return {"message": "OK"}\n'
740
+ elif method == "POST":
741
+ code += ' return {"message": "Created", "data": data}\n'
742
+ elif method in ["PUT", "PATCH"]:
743
+ code += f' return {{"id": {path_params[0] if path_params else "1"}, "updated": True}}\n'
744
+ elif method == "DELETE":
745
+ code += ' return None\n'
746
+
747
+ return code
748
+
749
+ def _generate_pydantic_model(self, name: str, fields: Dict[str, str]) -> str:
750
+ """Generate Pydantic model code"""
751
+ code = f'''from pydantic import BaseModel
752
+ from typing import Optional
753
+ from datetime import datetime
754
+
755
+
756
+ class {name}Base(BaseModel):
757
+ """Base {name} schema"""
758
+ '''
759
+ for field_name, field_type in fields.items():
760
+ code += f' {field_name}: {field_type}\n'
761
+
762
+ code += f'''
763
+
764
+ class {name}Create({name}Base):
765
+ """Schema for creating {name}"""
766
+ pass
767
+
768
+
769
+ class {name}Update(BaseModel):
770
+ """Schema for updating {name}"""
771
+ '''
772
+ for field_name, field_type in fields.items():
773
+ code += f' {field_name}: Optional[{field_type}] = None\n'
774
+
775
+ code += f'''
776
+
777
+ class {name}({name}Base):
778
+ """Full {name} schema with ID"""
779
+ id: int
780
+ created_at: datetime
781
+ updated_at: datetime
782
+
783
+ class Config:
784
+ from_attributes = True
785
+ '''
786
+ return code
787
+
788
+ def _generate_crud_router(self, model: str, prefix: str) -> str:
789
+ """Generate CRUD router code"""
790
+ model_lower = model.lower()
791
+
792
+ return f'''"""
793
+ {model} CRUD Router
794
+ """
795
+ from typing import List
796
+ from fastapi import APIRouter, Depends, HTTPException, status
797
+ from sqlalchemy.orm import Session
798
+ from app.db.database import get_db
799
+ # from app.models.{model_lower} import {model}
800
+ # from app.schemas.{model_lower} import {model}Create, {model}Update, {model} as {model}Schema
801
+
802
+ router = APIRouter(prefix="{prefix}", tags=["{model_lower}s"])
803
+
804
+
805
+ @router.get("/")
806
+ async def list_{model_lower}s(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
807
+ """List all {model_lower}s"""
808
+ # items = db.query({model}).offset(skip).limit(limit).all()
809
+ return {{"items": [], "total": 0}}
810
+
811
+
812
+ @router.post("/", status_code=status.HTTP_201_CREATED)
813
+ async def create_{model_lower}(data: dict, db: Session = Depends(get_db)):
814
+ """Create a new {model_lower}"""
815
+ # db_item = {model}(**data)
816
+ # db.add(db_item)
817
+ # db.commit()
818
+ # db.refresh(db_item)
819
+ return {{"message": "Created", "data": data}}
820
+
821
+
822
+ @router.get("/{{item_id}}")
823
+ async def get_{model_lower}(item_id: int, db: Session = Depends(get_db)):
824
+ """Get a {model_lower} by ID"""
825
+ # item = db.query({model}).filter({model}.id == item_id).first()
826
+ # if not item:
827
+ # raise HTTPException(status_code=404, detail="{model} not found")
828
+ return {{"id": item_id}}
829
+
830
+
831
+ @router.put("/{{item_id}}")
832
+ async def update_{model_lower}(item_id: int, data: dict, db: Session = Depends(get_db)):
833
+ """Update a {model_lower}"""
834
+ # item = db.query({model}).filter({model}.id == item_id).first()
835
+ # if not item:
836
+ # raise HTTPException(status_code=404, detail="{model} not found")
837
+ return {{"id": item_id, "updated": True}}
838
+
839
+
840
+ @router.delete("/{{item_id}}", status_code=status.HTTP_204_NO_CONTENT)
841
+ async def delete_{model_lower}(item_id: int, db: Session = Depends(get_db)):
842
+ """Delete a {model_lower}"""
843
+ # item = db.query({model}).filter({model}.id == item_id).first()
844
+ # if not item:
845
+ # raise HTTPException(status_code=404, detail="{model} not found")
846
+ # db.delete(item)
847
+ # db.commit()
848
+ return None
849
+ '''
850
+
851
+ def can_handle(self, request: str) -> float:
852
+ """Check if request is FastAPI-related"""
853
+ request_lower = request.lower()
854
+
855
+ # High confidence
856
+ high_conf = ["fastapi", "pydantic", "uvicorn", "api endpoint"]
857
+ for kw in high_conf:
858
+ if kw in request_lower:
859
+ return 0.9
860
+
861
+ # Medium confidence
862
+ med_conf = ["rest api", "endpoint", "router", "crud", "schema"]
863
+ for kw in med_conf:
864
+ if kw in request_lower:
865
+ return 0.6
866
+
867
+ return super().can_handle(request)
868
+
869
+ def handle_request(self, request: str, **kwargs) -> Optional[ActionResult]:
870
+ """Handle a natural language request"""
871
+ request_lower = request.lower()
872
+
873
+ if "scaffold" in request_lower or "new project" in request_lower:
874
+ # Extract project name
875
+ words = request.split()
876
+ name = "my_api"
877
+ for i, word in enumerate(words):
878
+ if word.lower() in ["project", "called", "named"]:
879
+ if i + 1 < len(words):
880
+ name = words[i + 1].strip("'\"")
881
+ break
882
+ return self.scaffold_project(name=name)
883
+
884
+ if "analyze" in request_lower:
885
+ return self.analyze_project()
886
+
887
+ return None