Skip to main content
Glama
PythonClassParser.ts4.92 kB
import { ParsedEntity, ParsedRelationship } from '../../../types.js'; import { EntityFactory } from '../../base/EntityFactory.js'; import { RelationshipBuilder } from '../../base/RelationshipBuilder.js'; import { PythonContentExtractor } from '../../extractors/python/PythonContentExtractor.js'; import { DocstringExtractor } from '../../extractors/python/DocstringExtractor.js'; import { PythonFrameworkDetector } from '../../framework-detection/PythonFrameworkDetector.js'; export class PythonClassParser { private contentExtractor = new PythonContentExtractor(); private docExtractor = new DocstringExtractor(); private frameworkDetector = new PythonFrameworkDetector(); parseClasses( content: string, filePath: string, moduleName: string, entities: ParsedEntity[], relationships: ParsedRelationship[], addEntity: (entity: Omit<ParsedEntity, 'project_id'>) => void, addRelationship: (rel: Omit<ParsedRelationship, 'project_id'>) => void ): void { const extractionResult = this.contentExtractor.extractContent(content, filePath); for (const parsedClass of extractionResult.classes) { const classId = `${moduleName}.${parsedClass.name}`; const qualifiedName = `${moduleName}.${parsedClass.name}`; // Extract documentation const documentation = parsedClass.startLine ? this.docExtractor.extractDocumentation(content, this.getPositionFromLine(content, parsedClass.startLine)) : undefined; // Extract decorators const decorators = this.extractDecorators(content, parsedClass.startLine || 1); // Create class entity const classEntity = EntityFactory.createClass( classId, parsedClass.name, qualifiedName, filePath, parsedClass.startLine, parsedClass.endLine, parsedClass.modifiers, documentation, decorators ); addEntity(classEntity); // Create module relationship const moduleId = moduleName; addRelationship(RelationshipBuilder.createBelongsTo(classId, moduleId, filePath)); // Handle inheritance if (parsedClass.extends && parsedClass.extends.length > 0) { for (const parentClass of parsedClass.extends) { const parentId = this.resolveType(parentClass.trim(), moduleName, extractionResult.imports); addRelationship(RelationshipBuilder.createExtends(classId, parentId, filePath)); } } } } private extractDecorators(content: string, startLine: number): any[] { const decorators: any[] = []; const lines = content.split('\n'); // Look backwards from the class declaration for decorators for (let i = startLine - 2; i >= 0; i--) { const line = lines[i].trim(); if (!line || line.startsWith('#')) continue; const decoratorMatch = line.match(/@([A-Za-z_][A-Za-z0-9_.]*)(?:\(([^)]*)\))?/); if (decoratorMatch) { const decoratorName = decoratorMatch[1]; const framework = this.frameworkDetector.detectFramework(decoratorName) || 'Unknown'; const category = this.frameworkDetector.categorizeAnnotation(decoratorName) || 'unknown'; decorators.unshift({ name: decoratorName, framework, category, parameters: decoratorMatch[2] ? decoratorMatch[2].split(',').map(p => p.trim()) : [] }); } else if (line && !line.startsWith('@')) { break; // Stop at non-decorator content } } return decorators; } private resolveType(typeName: string, moduleName: string, imports: any[]): string { // Remove generic type parameters const baseType = typeName.split('[')[0].trim(); // Check if it's already a qualified name if (baseType.includes('.')) { return baseType; } // Check imports for the type for (const imp of imports) { if (imp.items?.includes(baseType)) { return imp.module ? `${imp.module}.${baseType}` : baseType; } if (imp.alias === baseType) { return imp.module; } } // Check for built-in types if (this.isBuiltInType(baseType)) { return `builtins.${baseType}`; } // Default to same module return `${moduleName}.${baseType}`; } private isBuiltInType(typeName: string): boolean { const builtInTypes = [ 'object', 'str', 'int', 'float', 'bool', 'list', 'dict', 'tuple', 'set', 'Exception', 'BaseException', 'ValueError', 'TypeError', 'AttributeError' ]; return builtInTypes.includes(typeName); } private getPositionFromLine(content: string, lineNumber: number): number { const lines = content.split('\n'); let position = 0; for (let i = 0; i < Math.min(lineNumber - 1, lines.length); i++) { position += lines[i].length + 1; // +1 for newline } return position; } }

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