Skip to content

Commit f6ee9f7

Browse files
πŸ› Support factory function apps when entrypoint is configured (#101)
1 parent d5b1232 commit f6ee9f7

File tree

5 files changed

+73
-0
lines changed

5 files changed

+73
-0
lines changed

β€Žsrc/core/routerResolver.tsβ€Ž

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,25 @@ async function buildRouterGraphInternal(
113113
}
114114
}
115115

116+
// Factory function: if the entrypoint variable (e.g. "app" from "main:app")
117+
// is assigned via a factory function (`app = create_app()`) rather than a direct
118+
// FastAPI() constructor call, static analysis can't determine the type. If routes
119+
// are decorated with @app.get(...) etc. though, we know it must be a FastAPI instance.
120+
if (
121+
!appRouter &&
122+
targetVariable &&
123+
analysis.routes.some((r) => r.owner === targetVariable)
124+
) {
125+
appRouter = {
126+
variableName: targetVariable,
127+
type: "FastAPI",
128+
prefix: "",
129+
tags: [],
130+
line: 0,
131+
column: 0,
132+
}
133+
}
134+
116135
if (!appRouter || !analysis) {
117136
return null
118137
}

β€Žsrc/test/core/routerResolver.test.tsβ€Ž

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,5 +538,37 @@ suite("routerResolver", () => {
538538
assert.strictEqual(result.children[0].router.prefix, "/users")
539539
assert.ok(result.children[0].router.routes.length >= 2)
540540
})
541+
542+
test("infers FastAPI app when assigned via factory function (app = get_fastapi_app())", async () => {
543+
const result = await buildRouterGraph(
544+
fixtures.factoryFunc.mainPy,
545+
parser,
546+
fixtures.factoryFunc.root,
547+
nodeFileSystem,
548+
"app",
549+
)
550+
551+
assert.ok(
552+
result,
553+
"Should find app even when assigned via factory function",
554+
)
555+
assert.strictEqual(result.type, "FastAPI")
556+
assert.strictEqual(result.variableName, "app")
557+
assert.strictEqual(result.routes.length, 2)
558+
const paths = result.routes.map((r) => r.path)
559+
assert.ok(paths.includes("/1"))
560+
assert.ok(paths.includes("/2"))
561+
})
562+
563+
test("returns null without targetVariable when app is a factory function", async () => {
564+
const result = await buildRouterGraph(
565+
fixtures.factoryFunc.mainPy,
566+
parser,
567+
fixtures.factoryFunc.root,
568+
nodeFileSystem,
569+
)
570+
571+
assert.strictEqual(result, null)
572+
})
541573
})
542574
})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from fastapi import FastAPI
2+
3+
4+
def get_fastapi_app() -> FastAPI:
5+
return FastAPI()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from app import get_fastapi_app
2+
3+
app = get_fastapi_app()
4+
5+
6+
@app.get("/1")
7+
def one():
8+
return "Route one"
9+
10+
11+
@app.get("/2")
12+
def two():
13+
return "Route two"

β€Žsrc/test/testUtils.tsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export const fixtures = {
8383
projectRoot: uri(join(fixturesPath, "monorepo", "service")),
8484
mainPy: uri(join(fixturesPath, "monorepo", "service", "myapp", "main.py")),
8585
},
86+
factoryFunc: {
87+
root: uri(join(fixturesPath, "factory-func")),
88+
mainPy: uri(join(fixturesPath, "factory-func", "main.py")),
89+
},
8690
}
8791

8892
/**

0 commit comments

Comments
Β (0)