| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /**
- * Python Framework Resolver
- *
- * Handles Django, Flask, and FastAPI patterns.
- */
- import { Node } from '../../types';
- import { FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types';
- export const djangoResolver: FrameworkResolver = {
- name: 'django',
- detect(context: ResolutionContext): boolean {
- // Check for Django in requirements.txt or setup.py
- const requirements = context.readFile('requirements.txt');
- if (requirements && requirements.includes('django')) {
- return true;
- }
- const setup = context.readFile('setup.py');
- if (setup && setup.includes('django')) {
- return true;
- }
- const pyproject = context.readFile('pyproject.toml');
- if (pyproject && pyproject.includes('django')) {
- return true;
- }
- // Check for manage.py (Django signature)
- return context.fileExists('manage.py');
- },
- resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
- // Pattern 1: Model references
- if (ref.referenceName.endsWith('Model') || /^[A-Z][a-z]+$/.test(ref.referenceName)) {
- const result = resolveModel(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- // Pattern 2: View references
- if (ref.referenceName.endsWith('View') || ref.referenceName.endsWith('ViewSet')) {
- const result = resolveView(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- // Pattern 3: Form references
- if (ref.referenceName.endsWith('Form')) {
- const result = resolveForm(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- return null;
- },
- extractNodes(filePath: string, content: string): Node[] {
- const nodes: Node[] = [];
- const now = Date.now();
- // Extract URL patterns
- // path('route/', view, name='name')
- const urlPatterns = [
- /path\s*\(\s*['"]([^'"]+)['"],\s*(\w+)/g,
- /url\s*\(\s*r?['"]([^'"]+)['"],\s*(\w+)/g,
- ];
- for (const pattern of urlPatterns) {
- let match;
- while ((match = pattern.exec(content)) !== null) {
- const [, urlPath] = match;
- const line = content.slice(0, match.index).split('\n').length;
- nodes.push({
- id: `route:${filePath}:${urlPath}:${line}`,
- kind: 'route',
- name: urlPath!,
- qualifiedName: `${filePath}::route:${urlPath}`,
- filePath,
- startLine: line,
- endLine: line,
- startColumn: 0,
- endColumn: match[0].length,
- language: 'python',
- updatedAt: now,
- });
- }
- }
- return nodes;
- },
- };
- export const flaskResolver: FrameworkResolver = {
- name: 'flask',
- detect(context: ResolutionContext): boolean {
- const requirements = context.readFile('requirements.txt');
- if (requirements && (requirements.includes('flask') || requirements.includes('Flask'))) {
- return true;
- }
- const pyproject = context.readFile('pyproject.toml');
- if (pyproject && pyproject.includes('flask')) {
- return true;
- }
- // Check for Flask app pattern in common files
- const appFiles = ['app.py', 'application.py', 'main.py', '__init__.py'];
- for (const file of appFiles) {
- const content = context.readFile(file);
- if (content && content.includes('Flask(__name__)')) {
- return true;
- }
- }
- return false;
- },
- resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
- // Pattern 1: Blueprint references
- if (ref.referenceName.endsWith('_bp') || ref.referenceName.endsWith('_blueprint')) {
- const result = resolveBlueprint(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- return null;
- },
- extractNodes(filePath: string, content: string): Node[] {
- const nodes: Node[] = [];
- const now = Date.now();
- // Extract Flask route decorators
- // @app.route('/path') or @blueprint.route('/path')
- const routePattern = /@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]/g;
- let match;
- while ((match = routePattern.exec(content)) !== null) {
- const [, _appOrBp, routePath] = match;
- const line = content.slice(0, match.index).split('\n').length;
- nodes.push({
- id: `route:${filePath}:${routePath}:${line}`,
- kind: 'route',
- name: `${routePath}`,
- qualifiedName: `${filePath}::route:${routePath}`,
- filePath,
- startLine: line,
- endLine: line,
- startColumn: 0,
- endColumn: match[0].length,
- language: 'python',
- updatedAt: now,
- });
- }
- return nodes;
- },
- };
- export const fastapiResolver: FrameworkResolver = {
- name: 'fastapi',
- detect(context: ResolutionContext): boolean {
- const requirements = context.readFile('requirements.txt');
- if (requirements && requirements.includes('fastapi')) {
- return true;
- }
- const pyproject = context.readFile('pyproject.toml');
- if (pyproject && pyproject.includes('fastapi')) {
- return true;
- }
- // Check for FastAPI app pattern
- const appFiles = ['app.py', 'main.py', 'api.py'];
- for (const file of appFiles) {
- const content = context.readFile(file);
- if (content && content.includes('FastAPI()')) {
- return true;
- }
- }
- return false;
- },
- resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
- // Pattern 1: Router references
- if (ref.referenceName.endsWith('_router') || ref.referenceName === 'router') {
- const result = resolveRouter(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.8,
- resolvedBy: 'framework',
- };
- }
- }
- // Pattern 2: Dependency references
- if (ref.referenceName.startsWith('get_') || ref.referenceName.startsWith('Depends')) {
- const result = resolveDependency(ref.referenceName, context);
- if (result) {
- return {
- original: ref,
- targetNodeId: result,
- confidence: 0.75,
- resolvedBy: 'framework',
- };
- }
- }
- return null;
- },
- extractNodes(filePath: string, content: string): Node[] {
- const nodes: Node[] = [];
- const now = Date.now();
- // Extract FastAPI route decorators
- // @app.get('/path') or @router.post('/path')
- const routePattern = /@(\w+)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"]([^'"]+)['"]/g;
- let match;
- while ((match = routePattern.exec(content)) !== null) {
- const [, _appOrRouter, method, routePath] = match;
- const line = content.slice(0, match.index).split('\n').length;
- nodes.push({
- id: `route:${filePath}:${method!.toUpperCase()}:${routePath}:${line}`,
- kind: 'route',
- name: `${method!.toUpperCase()} ${routePath}`,
- qualifiedName: `${filePath}::${method!.toUpperCase()}:${routePath}`,
- filePath,
- startLine: line,
- endLine: line,
- startColumn: 0,
- endColumn: match[0].length,
- language: 'python',
- updatedAt: now,
- });
- }
- return nodes;
- },
- };
- // Helper functions
- function resolveModel(name: string, context: ResolutionContext): string | null {
- const modelDirs = ['models', 'app/models', 'src/models'];
- for (const dir of modelDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (file.startsWith(dir) && file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const modelNode = nodes.find(
- (n) => n.kind === 'class' && n.name === name
- );
- if (modelNode) {
- return modelNode.id;
- }
- }
- }
- }
- return null;
- }
- function resolveView(name: string, context: ResolutionContext): string | null {
- const viewDirs = ['views', 'app/views', 'src/views', 'api/views'];
- for (const dir of viewDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (file.startsWith(dir) && file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const viewNode = nodes.find(
- (n) => (n.kind === 'class' || n.kind === 'function') && n.name === name
- );
- if (viewNode) {
- return viewNode.id;
- }
- }
- }
- }
- return null;
- }
- function resolveForm(name: string, context: ResolutionContext): string | null {
- const formDirs = ['forms', 'app/forms', 'src/forms'];
- for (const dir of formDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (file.startsWith(dir) && file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const formNode = nodes.find(
- (n) => n.kind === 'class' && n.name === name
- );
- if (formNode) {
- return formNode.id;
- }
- }
- }
- }
- return null;
- }
- function resolveBlueprint(name: string, context: ResolutionContext): string | null {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if (file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const bpNode = nodes.find(
- (n) => n.kind === 'variable' && n.name === name
- );
- if (bpNode) {
- return bpNode.id;
- }
- }
- }
- return null;
- }
- function resolveRouter(name: string, context: ResolutionContext): string | null {
- const routerDirs = ['routers', 'api', 'routes', 'endpoints'];
- for (const dir of routerDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if ((file.startsWith(dir) || file.includes('/routers/')) && file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const routerNode = nodes.find(
- (n) => n.kind === 'variable' && n.name === name
- );
- if (routerNode) {
- return routerNode.id;
- }
- }
- }
- }
- return null;
- }
- function resolveDependency(name: string, context: ResolutionContext): string | null {
- const depDirs = ['dependencies', 'deps', 'core'];
- for (const dir of depDirs) {
- const allFiles = context.getAllFiles();
- for (const file of allFiles) {
- if ((file.startsWith(dir) || file.includes('/dependencies/')) && file.endsWith('.py')) {
- const nodes = context.getNodesInFile(file);
- const depNode = nodes.find(
- (n) => n.kind === 'function' && n.name === name
- );
- if (depNode) {
- return depNode.id;
- }
- }
- }
- }
- return null;
- }
|