Skip to content

Commit bda021e

Browse files
Refactor to accept module notation for entryPoint
1 parent 1f91757 commit bda021e

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@
303303
"type": "string",
304304
"default": "",
305305
"scope": "resource",
306-
"description": "Path to the main FastAPI application file (e.g., 'src/main.py'). If not set, the extension will search common locations."
306+
"description": "Entrypoint for the main FastAPI application in module notation (e.g., 'my_app.main:app'). If not set, the extension will search pyproject.toml and common locations."
307307
},
308308
"fastapi.codeLens.enabled": {
309309
"type": "boolean",

src/appDiscovery.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ import { vscodeFileSystem } from "./vscode/vscodeFileSystem"
1818

1919
export type { EntryPoint }
2020

21+
/**
22+
* Parses an entrypoint string in module:variable notation.
23+
* Supports formats like "my_app.main:app" or "main".
24+
* Returns the relative file path and optional variable name.
25+
*/
26+
export function parseEntrypointString(value: string): {
27+
relativePath: string
28+
variableName?: string
29+
} {
30+
const colonIndex = value.indexOf(":")
31+
const modulePath = colonIndex === -1 ? value : value.slice(0, colonIndex)
32+
const variableName =
33+
colonIndex === -1 ? undefined : value.slice(colonIndex + 1)
34+
35+
const relativePath = `${modulePath.replace(/\./g, "/")}.py`
36+
37+
return { relativePath, variableName }
38+
}
39+
2140
/**
2241
* Scans for common FastAPI entry point files (main.py, __init__.py).
2342
* Returns URI strings sorted by depth (shallower first).
@@ -64,18 +83,8 @@ async function parsePyprojectForEntryPoint(
6483
return null
6584
}
6685

67-
// Parse "my_app.main:app" or "api.py:app" format (variable name after : is optional)
68-
const colonIndex = entrypointValue.indexOf(":")
69-
const modulePath =
70-
colonIndex === -1 ? entrypointValue : entrypointValue.slice(0, colonIndex)
71-
const variableName =
72-
colonIndex === -1 ? undefined : entrypointValue.slice(colonIndex + 1)
73-
74-
// Handle both module format (api.module) and file format (api.py)
75-
const relativePath =
76-
modulePath.endsWith(".py") && !modulePath.includes("/")
77-
? modulePath // Simple file path: api.py -> api.py
78-
: `${modulePath.replace(/\./g, "/")}.py` // Module path: my_app.main -> my_app/main.py
86+
const { relativePath, variableName } =
87+
parseEntrypointString(entrypointValue)
7988
const fullUri = vscode.Uri.joinPath(folderUri, relativePath)
8089

8190
return (await vscodeFileSystem.exists(fullUri.toString()))
@@ -117,9 +126,9 @@ export async function discoverFastAPIApps(
117126

118127
// If user specified an entry point in settings, use that
119128
if (customEntryPoint) {
120-
const entryUri = customEntryPoint.startsWith("/")
121-
? vscode.Uri.file(customEntryPoint)
122-
: vscode.Uri.joinPath(folder.uri, customEntryPoint)
129+
const { relativePath, variableName } =
130+
parseEntrypointString(customEntryPoint)
131+
const entryUri = vscode.Uri.joinPath(folder.uri, relativePath)
123132

124133
if (!(await vscodeFileSystem.exists(entryUri.toString()))) {
125134
log(`Custom entry point not found: ${customEntryPoint}`)
@@ -130,7 +139,7 @@ export async function discoverFastAPIApps(
130139
}
131140

132141
log(`Using custom entry point: ${customEntryPoint}`)
133-
candidates = [{ filePath: entryUri.toString() }]
142+
candidates = [{ filePath: entryUri.toString(), variableName }]
134143
detectionMethod = "config"
135144
} else {
136145
// Otherwise, check pyproject.toml or auto-detect

src/test/appDiscovery.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as assert from "node:assert"
2+
import { parseEntrypointString } from "../appDiscovery"
3+
4+
suite("parseEntrypointString", () => {
5+
test("module notation with variable: my_app.main:app", () => {
6+
const result = parseEntrypointString("my_app.main:app")
7+
assert.strictEqual(result.relativePath, "my_app/main.py")
8+
assert.strictEqual(result.variableName, "app")
9+
})
10+
11+
test("module notation without variable: my_app.main", () => {
12+
const result = parseEntrypointString("my_app.main")
13+
assert.strictEqual(result.relativePath, "my_app/main.py")
14+
assert.strictEqual(result.variableName, undefined)
15+
})
16+
17+
test("deeply nested module: a.b.c.main:application", () => {
18+
const result = parseEntrypointString("a.b.c.main:application")
19+
assert.strictEqual(result.relativePath, "a/b/c/main.py")
20+
assert.strictEqual(result.variableName, "application")
21+
})
22+
23+
test("single module name: app", () => {
24+
const result = parseEntrypointString("app")
25+
assert.strictEqual(result.relativePath, "app.py")
26+
assert.strictEqual(result.variableName, undefined)
27+
})
28+
29+
test("single module with variable: app:my_app", () => {
30+
const result = parseEntrypointString("app:my_app")
31+
assert.strictEqual(result.relativePath, "app.py")
32+
assert.strictEqual(result.variableName, "my_app")
33+
})
34+
})

0 commit comments

Comments
 (0)