"""Validate whether a repository is a web service with HTTP endpoints. Checks dependency files for web framework references. Uses local filesystem for cloned repos, or GitHub API for remote repos (to avoid unnecessary clones). """ import base64 import json import subprocess from pathlib import Path # Web frameworks that indicate a service handles HTTP routes PYTHON_WEB_FRAMEWORKS = frozenset({ 'flask', 'fastapi', 'django', 'falcon', 'starlette', 'tornado', 'aiohttp', 'sanic', 'bottle', }) # Dependency files to check, in priority order PYTHON_DEP_FILES = [ 'requirements.txt', 'requirements/base.txt', 'requirements/production.txt', 'pyproject.toml', 'setup.py', 'setup.cfg', 'Pipfile', ] GITHUB_ORG = 'theorchard' def validate_service(repo_path: Path) -> tuple[bool, str]: """Check locally whether a cloned repo is a web service. Returns (is_service, reason). """ for rel_path in PYTHON_DEP_FILES: dep_file = repo_path / rel_path if not dep_file.exists(): continue try: content = dep_file.read_text().lower() except OSError: continue for fw in PYTHON_WEB_FRAMEWORKS: if fw in content: return True, f'{fw} in {rel_path}' return False, 'no web framework in dependencies' def validate_service_github(service_name: str, org: str = GITHUB_ORG) -> tuple[bool, str]: """Check via GitHub API whether a repo is a web service (no clone needed). Returns (is_service, reason). """ repo = f'{org}/{service_name}' # Check repo metadata (archived, existence) try: result = subprocess.run( ['gh', 'api', f'repos/{repo}', '--jq', '.archived'], capture_output=True, text=True, timeout=15, ) if result.returncode != 0: return False, 'repo not found on GitHub' if result.stdout.strip() == 'true': return False, 'repo is archived' except subprocess.TimeoutExpired: # Can't reach GitHub, let the clone attempt decide return True, 'GitHub check timed out, proceeding' # Check dependency files for web frameworks for dep_path in PYTHON_DEP_FILES: content = _fetch_github_file(repo, dep_path) if not content: continue content_lower = content.lower() for fw in PYTHON_WEB_FRAMEWORKS: if fw in content_lower: return True, f'{fw} in {dep_path}' return False, 'no web framework in dependencies' def _fetch_github_file(repo: str, path: str) -> str | None: """Fetch a file's content from GitHub via the gh CLI. Returns text or None.""" try: result = subprocess.run( ['gh', 'api', f'repos/{repo}/contents/{path}', '--jq', '.content'], capture_output=True, text=True, timeout=15, ) if result.returncode != 0 or not result.stdout.strip(): return None return base64.b64decode(result.stdout.strip()).decode('utf-8') except (subprocess.TimeoutExpired, Exception): return None