This commit is contained in:
Colby McHenry
2026-01-18 16:25:00 -06:00
parent 08ccabb5a9
commit cc6e7a5c89
57 changed files with 23315 additions and 1 deletions
+399
View File
@@ -0,0 +1,399 @@
/**
* 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;
}