diff --git a/package.json b/package.json index 2cee7d312f..b105bcf35d 100644 --- a/package.json +++ b/package.json @@ -805,6 +805,11 @@ "description": "%vscode-docker.debug.python.django%", "default": false }, + "fastapi": { + "type": "boolean", + "description": "%vscode-docker.debug.python.fastapi%", + "default": false + }, "jinja": { "type": "boolean", "description": "%vscode-docker.debug.python.jinja%", @@ -815,6 +820,7 @@ "description": "%vscode-docker.debug.python.projectType%", "enum": [ "django", + "fastapi", "flask", "general" ] diff --git a/package.nls.json b/package.nls.json index e4c78643ab..67763d8a70 100644 --- a/package.nls.json +++ b/package.nls.json @@ -30,6 +30,7 @@ "vscode-docker.debug.python.pathMappings.remoteRoot": "The container path.", "vscode-docker.debug.python.justMyCode": "Debug only user-written code.", "vscode-docker.debug.python.django": "Django debugging.", + "vscode-docker.debug.python.fastapi": "FastAPI debugging.", "vscode-docker.debug.python.jinja": "Jinja template debugging (e.g. Flask).", "vscode-docker.debug.python.projectType": "Type of the Python app.", "vscode-docker.debug.python.args": "Arguments passed to the Python app.", diff --git a/resources/templates/python/requirements.txt.template b/resources/templates/python/requirements.txt.template index 9ae2503d2d..291d051c1f 100644 --- a/resources/templates/python/requirements.txt.template +++ b/resources/templates/python/requirements.txt.template @@ -2,6 +2,10 @@ {{#if (eq platform 'Python: Django')}} django==3.1.1 {{/if}} +{{#if (eq platform 'Python: FastAPI')}} +fastapi[all]==0.63.0 +uvicorn[standard]==0.13.4 +{{/if}} {{#if (eq platform 'Python: Flask')}} flask==1.1.2 {{/if}} diff --git a/src/commands/containers/browseContainer.ts b/src/commands/containers/browseContainer.ts index 731d490d66..a7992c79ac 100644 --- a/src/commands/containers/browseContainer.ts +++ b/src/commands/containers/browseContainer.ts @@ -27,7 +27,7 @@ const commonWebPorts = [ 3001, // (Node.js) Sails.js 5001, // (.NET Core) ASP.NET SSL 5000, // (.NET Core) ASP.NET HTTP and (Python) Flask - 8000, // (Python) Django + 8000, // (Python) Django and FastAPI 8080, // (Node.js) 8081 // (Node.js) ]; diff --git a/src/debugging/python/PythonDebugHelper.ts b/src/debugging/python/PythonDebugHelper.ts index 8eea9e48ff..6ac35d9834 100644 --- a/src/debugging/python/PythonDebugHelper.ts +++ b/src/debugging/python/PythonDebugHelper.ts @@ -27,6 +27,7 @@ export interface PythonDebugOptions { justMyCode?: boolean; projectType?: PythonProjectType; django?: boolean; + fastapi?: boolean; jinja?: boolean; args?: string[]; } @@ -88,6 +89,7 @@ export class PythonDebugHelper implements DebugHelper { pathMappings: debugConfiguration.python.pathMappings, justMyCode: debugConfiguration.python.justMyCode ?? true, django: debugConfiguration.python.django || projectType === 'django', + fastapi: debugConfiguration.python.fastapi || projectType === 'fastapi', jinja: debugConfiguration.python.jinja || projectType === 'flask', dockerOptions: { containerName: containerName, @@ -126,6 +128,8 @@ export class PythonDebugHelper implements DebugHelper { switch (projectType) { case 'django': return 'Starting development server at (https?://\\S+|[0-9]+)'; + case 'fastapi': + return 'Uvicorn running on (https?://\\S+|[0-9]+)'; case 'flask': return 'Running on (https?://\\S+|[0-9]+)'; default: diff --git a/src/scaffolding/scaffoldDebugConfig.ts b/src/scaffolding/scaffoldDebugConfig.ts index dee76ed78d..ba0a3c77ee 100644 --- a/src/scaffolding/scaffoldDebugConfig.ts +++ b/src/scaffolding/scaffoldDebugConfig.ts @@ -16,7 +16,7 @@ export async function scaffoldDebugConfig(wizardContext: Partial[] = [ new ChooseWorkspaceFolderStep(), - new ChoosePlatformStep(['Node.js', '.NET: ASP.NET Core', '.NET: Core Console', 'Python: Django', 'Python: Flask', 'Python: General']), + new ChoosePlatformStep(['Node.js', '.NET: ASP.NET Core', '.NET: Core Console', 'Python: Django', 'Python: FastAPI', 'Python: Flask', 'Python: General']), ]; const wizard = new AzureWizard(wizardContext as ScaffoldingWizardContext, { diff --git a/src/scaffolding/wizard/ChoosePlatformStep.ts b/src/scaffolding/wizard/ChoosePlatformStep.ts index 2553de6aee..fa0c95190d 100644 --- a/src/scaffolding/wizard/ChoosePlatformStep.ts +++ b/src/scaffolding/wizard/ChoosePlatformStep.ts @@ -54,6 +54,7 @@ export class ChoosePlatformStep extends TelemetryPromptStep e subPath = path.join('netCore', `${this.fileType}.template`); break; case 'Python: Django': + case 'Python: FastAPI': case 'Python: Flask': case 'Python: General': subPath = path.join('python', `${this.fileType}.template`); diff --git a/src/scaffolding/wizard/python/PythonGatherInformationStep.ts b/src/scaffolding/wizard/python/PythonGatherInformationStep.ts index 2e7cb73440..1f30cfb890 100644 --- a/src/scaffolding/wizard/python/PythonGatherInformationStep.ts +++ b/src/scaffolding/wizard/python/PythonGatherInformationStep.ts @@ -18,6 +18,10 @@ export class PythonGatherInformationStep extends GatherInformationStep { + const { args, bindPort } = this.getCommonProps(wizardContext); + + let asgiModule: string; + + if ('module' in wizardContext.pythonArtifact) { + asgiModule = wizardContext.pythonArtifact.module; + } else if ('file' in wizardContext.pythonArtifact) { + asgiModule = wizardContext.pythonArtifact.file.replace(/\.[^/.]+$/, ''); + } + + // Replace forward-slashes with dots. + asgiModule = asgiModule.replace(/\//g, '.'); + + wizardContext.pythonCmdParts = [ + 'gunicorn', + '--bind', + `0.0.0.0:${bindPort}`, + '-k', + 'uvicorn.workers.UvicornWorker', + `${asgiModule}:app`, + ]; + + wizardContext.pythonDebugCmdParts = [ + 'sh', + '-c', + `${debugCmdPart} -m uvicorn ${asgiModule}:app ${args.join(' ')}`, + ]; + + wizardContext.debugPorts = [PythonDefaultDebugPort]; + } + private async getFlaskCmdParts(wizardContext: PythonScaffoldingWizardContext): Promise { const { args, bindPort } = this.getCommonProps(wizardContext); diff --git a/src/scaffolding/wizard/python/PythonScaffoldingWizardContext.ts b/src/scaffolding/wizard/python/PythonScaffoldingWizardContext.ts index e18a098855..a1b780087e 100644 --- a/src/scaffolding/wizard/python/PythonScaffoldingWizardContext.ts +++ b/src/scaffolding/wizard/python/PythonScaffoldingWizardContext.ts @@ -29,6 +29,8 @@ export function getPythonSubWizardOptions(wizardContext: ScaffoldingWizardContex if (wizardContext.platform === 'Python: Django' && (wizardContext.scaffoldType === 'all' || wizardContext.scaffoldType === 'compose')) { promptSteps.push(new ChoosePortsStep([PythonDefaultPorts.get('django')])); + } else if (wizardContext.platform === 'Python: FastAPI' && (wizardContext.scaffoldType === 'all' || wizardContext.scaffoldType === 'compose')) { + promptSteps.push(new ChoosePortsStep([PythonDefaultPorts.get('fastapi')])); } else if (wizardContext.platform === 'Python: Flask' && (wizardContext.scaffoldType === 'all' || wizardContext.scaffoldType === 'compose')) { promptSteps.push(new ChoosePortsStep([PythonDefaultPorts.get('flask')])); } diff --git a/src/tasks/python/PythonTaskHelper.ts b/src/tasks/python/PythonTaskHelper.ts index e3d312395e..34ff012324 100644 --- a/src/tasks/python/PythonTaskHelper.ts +++ b/src/tasks/python/PythonTaskHelper.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import { PythonScaffoldingOptions } from '../../debugging/DockerDebugScaffoldingProvider'; import { inferPythonArgs } from '../../utils/pythonUtils'; import { unresolveWorkspaceFolder } from "../../utils/resolveVariables"; @@ -66,6 +67,10 @@ export class PythonTaskHelper implements TaskHelper { } runOptions.module = 'flask'; runOptions.file = undefined; + } else if (options.projectType === 'fastapi') { + runOptions.args.unshift(`${path.basename(runOptions.file, '.py')}:app`); + runOptions.module = 'uvicorn'; + runOptions.file = undefined; } return [{ diff --git a/src/utils/platform.ts b/src/utils/platform.ts index ad9eb026ba..cd2f9cfed3 100644 --- a/src/utils/platform.ts +++ b/src/utils/platform.ts @@ -9,6 +9,7 @@ export const AllPlatforms = [ '.NET: ASP.NET Core', '.NET: Core Console', 'Python: Django', + 'Python: FastAPI', 'Python: Flask', 'Python: General', 'Java', diff --git a/src/utils/pythonUtils.ts b/src/utils/pythonUtils.ts index 92bf258edd..cb9ae885fb 100644 --- a/src/utils/pythonUtils.ts +++ b/src/utils/pythonUtils.ts @@ -7,12 +7,13 @@ import * as fse from 'fs-extra'; import * as os from 'os'; import { Platform } from './platform'; -export type PythonProjectType = 'django' | 'flask' | 'general'; +export type PythonProjectType = 'django' | 'fastapi' | 'flask' | 'general'; export const PythonFileExtension = ".py"; export const PythonDefaultDebugPort: number = 5678; export const PythonDefaultPorts = new Map([ ['django', 8000], + ['fastapi', 8000], ['flask', 5000], ['general', undefined], ]); @@ -35,6 +36,11 @@ export function inferPythonArgs(projectType: PythonProjectType, ports: number[]) '--nothreading', '--noreload' ]; + case 'fastapi': + return [ + '--host', '0.0.0.0', + '--port', `${ports !== undefined ? ports[0] : PythonDefaultPorts.get(projectType)}`, + ] case 'flask': return [ 'run', @@ -52,6 +58,8 @@ export function getPythonProjectType(platform: Platform): PythonProjectType | un switch (platform) { case 'Python: Django': return 'django'; + case 'Python: FastAPI': + return 'fastapi'; case 'Python: Flask': return 'flask'; case 'Python: General':