|
|
|
"""
|
|
Route API - ルート一覧とテスト機能のAPIエンドポイント
|
|
CI/CDパイプライン用のAPI
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from typing import Dict, List, Any
|
|
import os
|
|
import sys
|
|
import re
|
|
import importlib
|
|
from pathlib import Path
|
|
|
|
router = APIRouter()
|
|
|
|
class RouteScanner:
|
|
"""ルートスキャナー - artisan.pyのロジックを再利用"""
|
|
|
|
def __init__(self):
|
|
self.project_root = Path(__file__).parent.parent
|
|
|
|
def scan_all_routes(self) -> Dict[str, Any]:
|
|
"""全ルートをスキャン"""
|
|
return {
|
|
"fastapi_routes": self._scan_fastapi_routes(),
|
|
"gradio_interfaces": self._scan_gradio_interfaces(),
|
|
"django_urls": self._scan_django_urls(),
|
|
"summary": self._get_summary()
|
|
}
|
|
|
|
def scan_active_routes(self) -> Dict[str, Any]:
|
|
"""アクティブなルートのみスキャン"""
|
|
return {
|
|
"fastapi_routes": self._scan_active_fastapi_routes(),
|
|
"gradio_interfaces": self._scan_active_gradio_interfaces(),
|
|
"django_urls": self._scan_active_django_urls(),
|
|
"summary": self._get_active_summary()
|
|
}
|
|
|
|
def _scan_fastapi_routes(self) -> List[Dict[str, str]]:
|
|
"""FastAPIルートをスキャン"""
|
|
routes = []
|
|
|
|
|
|
main_files = ["mysite/asgi.py", "app.py", "main.py"]
|
|
for main_file in main_files:
|
|
main_path = self.project_root / main_file
|
|
if main_path.exists():
|
|
routes.extend(self._extract_routes_from_file(main_path, main_file))
|
|
|
|
|
|
routers_dir = self.project_root / "routers"
|
|
if routers_dir.exists():
|
|
for py_file in routers_dir.glob("**/*.py"):
|
|
if py_file.name != "__init__.py":
|
|
relative_path = str(py_file.relative_to(self.project_root))
|
|
routes.extend(self._extract_routes_from_file(py_file, relative_path))
|
|
|
|
return routes
|
|
|
|
def _scan_active_fastapi_routes(self) -> List[Dict[str, str]]:
|
|
"""アクティブなFastAPIルートのみスキャン"""
|
|
routes = []
|
|
|
|
|
|
main_files = ["mysite/asgi.py", "app.py", "main.py"]
|
|
for main_file in main_files:
|
|
main_path = self.project_root / main_file
|
|
if main_path.exists():
|
|
routes.extend(self._extract_routes_from_file(main_path, main_file))
|
|
|
|
|
|
routers_dir = self.project_root / "routers"
|
|
if routers_dir.exists():
|
|
|
|
for py_file in routers_dir.glob("*.py"):
|
|
if py_file.name != "__init__.py":
|
|
relative_path = str(py_file.relative_to(self.project_root))
|
|
routes.extend(self._extract_routes_from_file(py_file, relative_path))
|
|
|
|
|
|
for subdir in routers_dir.iterdir():
|
|
if subdir.is_dir() and subdir.name.startswith('gra_'):
|
|
for py_file in subdir.glob("*.py"):
|
|
if py_file.name != "__init__.py":
|
|
relative_path = str(py_file.relative_to(self.project_root))
|
|
routes.extend(self._extract_gradio_functions_as_routes(py_file, relative_path))
|
|
|
|
return routes
|
|
|
|
def _scan_gradio_interfaces(self) -> List[Dict[str, Any]]:
|
|
"""Gradioインターフェースをスキャン"""
|
|
interfaces = []
|
|
controllers_dir = self.project_root / "controllers"
|
|
|
|
if controllers_dir.exists():
|
|
for subdir in controllers_dir.iterdir():
|
|
if subdir.is_dir() and subdir.name.startswith('gra_'):
|
|
py_files = [f for f in subdir.glob("*.py") if f.name != "__init__.py"]
|
|
for py_file in py_files:
|
|
interface_info = self._analyze_gradio_interface(py_file, subdir.name)
|
|
if interface_info:
|
|
interfaces.append(interface_info)
|
|
|
|
return interfaces
|
|
|
|
def _scan_active_gradio_interfaces(self) -> List[Dict[str, Any]]:
|
|
"""アクティブなGradioインターフェースのみスキャン"""
|
|
return self._scan_gradio_interfaces()
|
|
|
|
def _scan_django_urls(self) -> List[Dict[str, str]]:
|
|
"""DjangoURLをスキャン"""
|
|
urls = []
|
|
|
|
|
|
for urls_file in self.project_root.rglob("urls.py"):
|
|
if "workspace" not in str(urls_file):
|
|
relative_path = str(urls_file.relative_to(self.project_root))
|
|
urls.extend(self._extract_django_patterns(urls_file, relative_path))
|
|
|
|
return urls
|
|
|
|
def _scan_active_django_urls(self) -> List[Dict[str, str]]:
|
|
"""アクティブなDjangoURLのみスキャン"""
|
|
urls = []
|
|
|
|
|
|
active_files = ["mysite/urls.py", "polls/urls.py"]
|
|
for django_file in active_files:
|
|
urls_path = self.project_root / django_file
|
|
if urls_path.exists():
|
|
urls.extend(self._extract_django_patterns(urls_path, django_file))
|
|
|
|
return urls
|
|
|
|
def _extract_routes_from_file(self, file_path: Path, source: str) -> List[Dict[str, str]]:
|
|
"""ファイルからルートを抽出"""
|
|
routes = []
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
|
|
route_patterns = [
|
|
r'@app\.(get|post|put|delete|patch)\(["\']([^"\']+)["\']',
|
|
r'@router\.(get|post|put|delete|patch)\(["\']([^"\']+)["\']'
|
|
]
|
|
|
|
for pattern in route_patterns:
|
|
matches = re.findall(pattern, content)
|
|
for method, path in matches:
|
|
routes.append({
|
|
"method": method.upper(),
|
|
"path": path,
|
|
"source": source,
|
|
"type": "fastapi"
|
|
})
|
|
except Exception as e:
|
|
print(f"Error reading {file_path}: {e}")
|
|
|
|
return routes
|
|
|
|
def _extract_gradio_functions_as_routes(self, file_path: Path, source: str) -> List[Dict[str, str]]:
|
|
"""Gradioファイルから関数をルートとして抽出"""
|
|
routes = []
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
|
|
function_pattern = r'def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\('
|
|
functions = re.findall(function_pattern, content)
|
|
|
|
|
|
user_functions = [f for f in functions if not f.startswith('_')]
|
|
|
|
for func in user_functions:
|
|
routes.append({
|
|
"method": "GRADIO",
|
|
"path": f"/gradio/{source.replace('/', '_')}#{func}",
|
|
"source": source,
|
|
"type": "gradio_function",
|
|
"function": func
|
|
})
|
|
except Exception as e:
|
|
print(f"Error reading gradio file: {e}")
|
|
|
|
return routes
|
|
|
|
def _analyze_gradio_interface(self, file_path: Path, category: str) -> Dict[str, Any]:
|
|
"""Gradioインターフェースを解析"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
|
|
gradio_types = re.findall(r'gr\.(Interface|Blocks|TabbedInterface|ChatInterface)', content)
|
|
|
|
|
|
functions = re.findall(r'def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', content)
|
|
user_functions = [f for f in functions if not f.startswith('_')]
|
|
|
|
return {
|
|
"category": category,
|
|
"file": file_path.name,
|
|
"path": str(file_path.relative_to(self.project_root)),
|
|
"gradio_types": list(set(gradio_types)),
|
|
"functions": user_functions,
|
|
"type": "gradio_interface"
|
|
}
|
|
except Exception as e:
|
|
print(f"Error analyzing gradio interface: {e}")
|
|
return None
|
|
|
|
def _extract_django_patterns(self, file_path: Path, source: str) -> List[Dict[str, str]]:
|
|
"""DjangoのURLパターンを抽出"""
|
|
patterns = []
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
|
|
url_patterns = [
|
|
r'path\(["\']([^"\']*)["\']',
|
|
r'url\(["\']([^"\']*)["\']'
|
|
]
|
|
|
|
for pattern in url_patterns:
|
|
matches = re.findall(pattern, content)
|
|
for url in matches:
|
|
patterns.append({
|
|
"method": "PATH",
|
|
"path": url or "/",
|
|
"source": source,
|
|
"type": "django"
|
|
})
|
|
except Exception as e:
|
|
print(f"Error reading Django URLs: {e}")
|
|
|
|
return patterns
|
|
|
|
def _get_summary(self) -> Dict[str, int]:
|
|
"""ルートの統計情報"""
|
|
fastapi = len(self._scan_fastapi_routes())
|
|
gradio = len(self._scan_gradio_interfaces())
|
|
django = len(self._scan_django_urls())
|
|
|
|
return {
|
|
"total_routes": fastapi + gradio + django,
|
|
"fastapi_routes": fastapi,
|
|
"gradio_interfaces": gradio,
|
|
"django_urls": django
|
|
}
|
|
|
|
def _get_active_summary(self) -> Dict[str, int]:
|
|
"""アクティブルートの統計情報"""
|
|
fastapi = len(self._scan_active_fastapi_routes())
|
|
gradio = len(self._scan_active_gradio_interfaces())
|
|
django = len(self._scan_active_django_urls())
|
|
|
|
return {
|
|
"total_active_routes": fastapi + gradio + django,
|
|
"active_fastapi_routes": fastapi,
|
|
"active_gradio_interfaces": gradio,
|
|
"active_django_urls": django
|
|
}
|
|
|
|
|
|
scanner = RouteScanner()
|
|
|
|
@router.get("/routes/all")
|
|
async def get_all_routes():
|
|
"""全ルート一覧を取得"""
|
|
try:
|
|
return scanner.scan_all_routes()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Route scanning failed: {str(e)}")
|
|
|
|
@router.get("/routes/active")
|
|
async def get_active_routes():
|
|
"""アクティブなルートのみ取得"""
|
|
try:
|
|
return scanner.scan_active_routes()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Active route scanning failed: {str(e)}")
|
|
|
|
@router.get("/routes/summary")
|
|
async def get_routes_summary():
|
|
"""ルートのサマリー情報を取得"""
|
|
try:
|
|
return {
|
|
"all_routes_summary": scanner._get_summary(),
|
|
"active_routes_summary": scanner._get_active_summary()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Summary generation failed: {str(e)}")
|
|
|
|
@router.get("/routes/test")
|
|
async def test_routes():
|
|
"""ルートの基本テスト"""
|
|
try:
|
|
active_routes = scanner.scan_active_routes()
|
|
|
|
|
|
test_results = {
|
|
"total_tests": 0,
|
|
"passed_tests": 0,
|
|
"failed_tests": 0,
|
|
"test_details": []
|
|
}
|
|
|
|
|
|
for route in active_routes["fastapi_routes"]:
|
|
test_results["total_tests"] += 1
|
|
test_detail = {
|
|
"type": "fastapi",
|
|
"method": route["method"],
|
|
"path": route["path"],
|
|
"source": route["source"],
|
|
"status": "passed",
|
|
"message": "Route definition found"
|
|
}
|
|
test_results["test_details"].append(test_detail)
|
|
test_results["passed_tests"] += 1
|
|
|
|
|
|
for interface in active_routes["gradio_interfaces"]:
|
|
test_results["total_tests"] += 1
|
|
test_detail = {
|
|
"type": "gradio",
|
|
"category": interface["category"],
|
|
"file": interface["file"],
|
|
"functions": len(interface["functions"]),
|
|
"status": "passed" if interface["functions"] else "warning",
|
|
"message": f"Found {len(interface['functions'])} functions" if interface["functions"] else "No functions found"
|
|
}
|
|
test_results["test_details"].append(test_detail)
|
|
if interface["functions"]:
|
|
test_results["passed_tests"] += 1
|
|
else:
|
|
test_results["failed_tests"] += 1
|
|
|
|
|
|
for url in active_routes["django_urls"]:
|
|
test_results["total_tests"] += 1
|
|
test_detail = {
|
|
"type": "django",
|
|
"method": url["method"],
|
|
"path": url["path"],
|
|
"source": url["source"],
|
|
"status": "passed",
|
|
"message": "Django URL pattern found"
|
|
}
|
|
test_results["test_details"].append(test_detail)
|
|
test_results["passed_tests"] += 1
|
|
|
|
return test_results
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Route testing failed: {str(e)}")
|
|
|
|
@router.get("/health")
|
|
async def route_api_health():
|
|
"""Route API ヘルスチェック"""
|
|
return {
|
|
"status": "healthy",
|
|
"service": "route_api",
|
|
"scanner": "ready"
|
|
}
|
|
|