diff --git a/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts b/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts index 30783227cd45..032a38b6740c 100644 --- a/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts @@ -10,6 +10,7 @@ import { IDynamicDebugConfigurationService } from '../types'; import { IFileSystem } from '../../../common/platform/types'; import { IPathUtils } from '../../../common/types'; import { DebuggerTypeName } from '../../constants'; +import { asyncFilter } from '../../../common/utils/arrayUtils'; const workspaceFolderToken = '${workspaceFolder}'; @@ -31,19 +32,64 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf justMyCode: true, }); - const djangoManagePath = await this.fs.search(path.join(folder.uri.fsPath, '**/manage.py')); - if (djangoManagePath.length) { - const managePath = path.relative(folder.uri.fsPath, djangoManagePath[0]); + const djangoManagePath = await this.getDjangoPath(folder); + if (djangoManagePath) { providers.push({ name: 'Dynamic Python: Django', type: DebuggerTypeName, request: 'launch', - program: `${workspaceFolderToken}${this.pathUtils.separator}${managePath}`, + program: `${workspaceFolderToken}${this.pathUtils.separator}${djangoManagePath}`, args: ['runserver'], django: true, justMyCode: true, }); } + + let fastApiPath = await this.getFastApiPath(folder); + if (fastApiPath) { + fastApiPath = path + .relative(folder.uri.fsPath, fastApiPath) + .replaceAll(this.pathUtils.separator, '.') + .replace('.py', ''); + providers.push({ + name: 'Dynamic Python: FastAPI', + type: DebuggerTypeName, + request: 'launch', + module: 'uvicorn', + args: [`${fastApiPath}:app`], + jinja: true, + justMyCode: true, + }); + } + return providers; } + + private async getDjangoPath(folder: WorkspaceFolder) { + const possiblePaths = await this.getPossiblePaths(folder, ['manage.py', '*/manage.py']); + + return possiblePaths.length ? path.relative(folder.uri.fsPath, possiblePaths[0]) : null; + } + + private async getFastApiPath(folder: WorkspaceFolder) { + const possiblePaths = await this.getPossiblePaths(folder, ['main.py', 'app.py', '*/main.py', '*/app.py']); + const regExpression = /app\s*=\s*FastAPI\(/; + const flaskPaths = await asyncFilter(possiblePaths, async (possiblePath) => + regExpression.exec((await this.fs.readFile(possiblePath)).toString()), + ); + + return flaskPaths.length ? flaskPaths[0] : null; + } + + private async getPossiblePaths(folder: WorkspaceFolder, globPatterns: string[]): Promise { + const foundPathsPromises = (await Promise.allSettled( + globPatterns.map( + async (pattern): Promise => this.fs.search(path.join(folder.uri.fsPath, pattern)), + ), + )) as { status: string; value: [] }[]; + const results: string[] = []; + foundPathsPromises.forEach((result) => results.push(...result.value)); + + return results; + } }