Skip to main content
Glama
PythonAnnotationExtractor.ts8.4 kB
import { BaseAnnotationExtractor, AnnotationExtractionResult, ParsedAnnotationParameter } from '../base/AnnotationExtractor.js'; import { AnnotationInfo } from '../../../../types.js'; /** * Python-specific decorator extraction */ export class PythonAnnotationExtractor extends BaseAnnotationExtractor { extractAnnotations( content: string, entityPosition: number ): AnnotationExtractionResult { const errors: string[] = []; const annotations: AnnotationInfo[] = []; try { const beforeEntity = content.substring(0, entityPosition); const lines = beforeEntity.split('\n'); // Scan backwards from entity to find decorators for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i].trim(); if (this.isAnnotationLine(line)) { const match = line.match(/@([A-Za-z_][A-Za-z0-9_.]*)(?:\(([^)]*)\))?/); if (match) { const decoratorName = match[1]; const parametersString = match[2]; annotations.unshift({ name: decoratorName, type: 'decorator', parameters: this.parseAnnotationParameters(parametersString), source_line: i + 1, framework: this.detectFramework(decoratorName), category: this.categorizeAnnotation(decoratorName) }); } } else if (line && !this.isCommentLine(line)) { // Stop on non-comment, non-decorator content break; } } } catch (error) { errors.push(`Error extracting Python decorators: ${error instanceof Error ? error.message : String(error)}`); } return { annotations, errors }; } parseAnnotationParameters(parametersString?: string): ParsedAnnotationParameter[] { if (!parametersString || !parametersString.trim()) { return []; } const params: ParsedAnnotationParameter[] = []; const paramString = parametersString.trim(); // Handle different parameter patterns if (paramString.includes('=')) { // Named parameters like @decorator(param1=value1, param2=value2) const assignments = this.splitParameters(paramString); for (const assignment of assignments) { const parts = assignment.split('=').map(p => p.trim()); if (parts.length === 2) { params.push({ name: parts[0], value: this.removeQuotes(parts[1]), type: this.inferParameterType(parts[1]) }); } } } else { // Positional parameters like @decorator(value1, value2) const values = this.splitParameters(paramString); for (const value of values) { params.push({ value: this.removeQuotes(value.trim()), type: this.inferParameterType(value.trim()) }); } } return params; } detectFramework(decoratorName: string): string | undefined { const frameworkMap: Record<string, string> = { // Flask 'app.route': 'Flask', 'route': 'Flask', 'before_request': 'Flask', 'after_request': 'Flask', 'teardown_request': 'Flask', 'context_processor': 'Flask', 'template_filter': 'Flask', 'template_global': 'Flask', // Django 'login_required': 'Django', 'permission_required': 'Django', 'user_passes_test': 'Django', 'csrf_exempt': 'Django', 'require_http_methods': 'Django', 'require_GET': 'Django', 'require_POST': 'Django', 'require_safe': 'Django', 'cache_page': 'Django', 'never_cache': 'Django', 'vary_on_headers': 'Django', 'vary_on_cookie': 'Django', // FastAPI 'app.get': 'FastAPI', 'app.post': 'FastAPI', 'app.put': 'FastAPI', 'app.delete': 'FastAPI', 'app.patch': 'FastAPI', 'app.options': 'FastAPI', 'app.head': 'FastAPI', 'app.trace': 'FastAPI', 'Depends': 'FastAPI', 'HTTPException': 'FastAPI', // Pytest 'pytest.fixture': 'Pytest', 'pytest.mark.parametrize': 'Pytest', 'pytest.mark.skip': 'Pytest', 'pytest.mark.skipif': 'Pytest', 'pytest.mark.xfail': 'Pytest', 'pytest.mark.slow': 'Pytest', 'fixture': 'Pytest', 'mark.parametrize': 'Pytest', 'mark.skip': 'Pytest', 'mark.skipif': 'Pytest', 'mark.xfail': 'Pytest', // Celery 'task': 'Celery', 'periodic_task': 'Celery', 'shared_task': 'Celery', // SQLAlchemy 'validates': 'SQLAlchemy', 'reconstructor': 'SQLAlchemy', 'hybrid_property': 'SQLAlchemy', 'hybrid_method': 'SQLAlchemy', // Pydantic 'validator': 'Pydantic', 'root_validator': 'Pydantic', 'field_validator': 'Pydantic', 'model_validator': 'Pydantic', // Python built-ins 'property': 'Python', 'staticmethod': 'Python', 'classmethod': 'Python', 'cached_property': 'Python', 'lru_cache': 'Python', 'singledispatch': 'Python', 'wraps': 'Python', 'dataclass': 'Python', 'total_ordering': 'Python', // Click 'click.command': 'Click', 'click.group': 'Click', 'click.option': 'Click', 'click.argument': 'Click', 'command': 'Click', 'group': 'Click', 'option': 'Click', 'argument': 'Click', // Typing 'overload': 'Typing', 'final': 'Typing', 'runtime_checkable': 'Typing', // Deprecated 'deprecated': 'Deprecated' }; return frameworkMap[decoratorName]; } categorizeAnnotation(decoratorName: string): string | undefined { const categoryMap: Record<string, string> = { // Web/API 'app.route': 'web', 'route': 'web', 'app.get': 'web', 'app.post': 'web', 'app.put': 'web', 'app.delete': 'web', 'app.patch': 'web', 'require_http_methods': 'web', 'require_GET': 'web', 'require_POST': 'web', // Authentication/Security 'login_required': 'security', 'permission_required': 'security', 'user_passes_test': 'security', 'csrf_exempt': 'security', // Caching 'cache_page': 'caching', 'never_cache': 'caching', 'lru_cache': 'caching', 'cached_property': 'caching', // Testing 'pytest.fixture': 'testing', 'fixture': 'testing', 'pytest.mark.parametrize': 'testing', 'mark.parametrize': 'testing', 'pytest.mark.skip': 'testing', 'mark.skip': 'testing', 'pytest.mark.skipif': 'testing', 'mark.skipif': 'testing', 'pytest.mark.xfail': 'testing', 'mark.xfail': 'testing', // Data/Persistence 'validates': 'persistence', 'validator': 'validation', 'root_validator': 'validation', 'field_validator': 'validation', 'model_validator': 'validation', // Language Features 'property': 'language', 'staticmethod': 'language', 'classmethod': 'language', 'dataclass': 'language', 'total_ordering': 'language', 'overload': 'language', 'final': 'language', 'runtime_checkable': 'language', // Lifecycle/Events 'before_request': 'lifecycle', 'after_request': 'lifecycle', 'teardown_request': 'lifecycle', 'reconstructor': 'lifecycle', // Async/Tasks 'task': 'async', 'periodic_task': 'async', 'shared_task': 'async', // CLI 'click.command': 'cli', 'command': 'cli', 'click.group': 'cli', 'group': 'cli', 'click.option': 'cli', 'option': 'cli', 'click.argument': 'cli', 'argument': 'cli', // Utilities 'wraps': 'utility', 'singledispatch': 'utility', 'deprecated': 'utility', 'vary_on_headers': 'utility', 'vary_on_cookie': 'utility', // Templates 'template_filter': 'template', 'template_global': 'template', 'context_processor': 'template' }; return categoryMap[decoratorName]; } protected isCommentLine(line: string): boolean { return line.trim().startsWith('#'); } protected isAnnotationLine(line: string): boolean { return line.trim().startsWith('@'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonnoC/CodeRAG'

If you have feedback or need assistance with the MCP directory API, please join our Discord server