✨ feat(vscode): 代码阅读的 FastAPI 路由器单独一张「路由器」卡片(前缀、标签、依赖、默认响应,接口表列挂在它上面的接口,函数名 ⌘ + 点击跳到接口卡片);接口卡片写所属路由器
This commit is contained in:
1 parent
b4e4879570
commit
71641bdc41
6 files changed
+282
-30
No files matched your search
@@ -69,6 +69,7 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
expect(propertyLines(properties(card))).toEqual([
|
||||
'名称: update_user',
|
||||
'说明: 更新用户信息。\n\n管理员可更新任意用户,普通用户只能更新自己的基础资料。',
|
||||
'路由器: router',
|
||||
'标签: 用户管理',
|
||||
'异步: 是',
|
||||
'依赖: get_db(db)、get_current_user(current_user)',
|
||||
@@ -82,7 +83,7 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
' 403 | Forbidden | 无权更新此用户信息;无权修改角色;无权修改启用状态;无权修改密码',
|
||||
' 422 | 参数校验失败 | FastAPI 自动返回',
|
||||
])
|
||||
expect(properties(card).map(row => row.columns)).toEqual([undefined, undefined, undefined, undefined, undefined, ['项', '值'], ['状态码', '内容', '说明']])
|
||||
expect(properties(card).map(row => row.columns)).toEqual([undefined, undefined, undefined, undefined, undefined, undefined, ['项', '值'], ['状态码', '内容', '说明']])
|
||||
expect(card.attributes).toMatchObject({ kind: 'fastapi.route', access: 'public', async: 'true', httpMethod: 'PUT' })
|
||||
expect(fastapiRule.attributeNames?.('zh')).toEqual({ attributes: { httpMethod: '请求方法' }, values: {} })
|
||||
expect(ROUTES.slice(card.defines?.startIndex, card.defines?.endIndex)).toBe('update_user')
|
||||
@@ -123,6 +124,7 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
it('luffy 的 list_skills:Annotated 里的 Query 是查询参数(类型取 Annotated 的第一项),Depends 是依赖,参数名是 _ 的只写依赖', async () => {
|
||||
expect(propertyLines(properties(await route(SKILL_HUB, 'list_skills')))).toEqual([
|
||||
'名称: list_skills',
|
||||
'路由器: skill_hub_router',
|
||||
'标签: skill-hub',
|
||||
'异步: 是',
|
||||
'依赖: get_db(db)、get_current_user',
|
||||
@@ -142,7 +144,7 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
' 404 | HTTPException | f"技能不存在: {slug}"',
|
||||
' 422 | 参数校验失败 | FastAPI 自动返回',
|
||||
])
|
||||
expect(propertyLines(properties(await route(SKILL_HUB, 'install'))).slice(4)).toEqual([
|
||||
expect(propertyLines(properties(await route(SKILL_HUB, 'install'))).slice(5)).toEqual([
|
||||
'请求: POST /skill-hub/apps/{app_id}/skills/{slug}',
|
||||
' URL | /skill-hub/apps/{app_id}/skills/{slug}',
|
||||
' 方法 | POST',
|
||||
@@ -157,6 +159,7 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
expect(propertyLines(properties(await route(CONSOLE, 'create_user', known({ Forbidden: '403' }))))).toEqual([
|
||||
'名称: create_user',
|
||||
'说明: 创建新用户。\n\n需要管理员权限。密码会自动哈希。',
|
||||
'路由器: router',
|
||||
'标签: 用户管理-运维',
|
||||
'异步: 是',
|
||||
'依赖: get_db(db)、require_users_create',
|
||||
@@ -239,6 +242,7 @@ def remove(item_id: int):
|
||||
expect(propertyLines(properties(await route(source, 'remove')))).toEqual([
|
||||
'名称: remove',
|
||||
'说明: 删掉一个物品。',
|
||||
'路由器: router',
|
||||
'标签: 物品、管理',
|
||||
'异步: 否',
|
||||
'依赖: verify_token、audit',
|
||||
@@ -265,6 +269,7 @@ async def health() -> dict:
|
||||
`
|
||||
expect(propertyLines(properties(await route(source, 'health')))).toEqual([
|
||||
'名称: health',
|
||||
'路由器: app',
|
||||
'异步: 是',
|
||||
'装饰器: @cache(60)',
|
||||
'请求: GET /health',
|
||||
@@ -303,6 +308,84 @@ def e():
|
||||
})
|
||||
})
|
||||
|
||||
/** 名字是 name 的路由器卡片。 */
|
||||
async function routerCard(source: string, name: string): Promise<BlockView> {
|
||||
const found = flatten(await views(source)).find(view => view.kind === 'fastapi.router' && view.defines && source.slice(view.defines.startIndex, view.defines.endIndex) === name)
|
||||
if (!found)
|
||||
throw new Error(`没有 ${name} 的路由器卡片`)
|
||||
return found
|
||||
}
|
||||
|
||||
describe('fastapi 库规则:路由器卡片', () => {
|
||||
it('luffy 的 router:名称 / 前缀 / 标签,接口表列这份文件里挂在它上面的接口(方法 / 完整路径 / 函数 / docstring 第一行)', async () => {
|
||||
const card = await routerCard(ROUTES, 'router')
|
||||
expect(card.badge).toEqual({ label: '路由器', color: { dark: '#7aa2f7', light: '#34548a' }, icon: 'network' })
|
||||
expect(propertyLines(properties(card))).toEqual([
|
||||
'名称: router',
|
||||
'前缀: /users',
|
||||
'标签: 用户管理',
|
||||
'接口: 3 个',
|
||||
' GET | /users/{user_id} | get_user | 获取用户详情。',
|
||||
' PUT | /users/{user_id} | update_user | 更新用户信息。',
|
||||
' POST | /users/me/avatar | upload_current_user_avatar | 上传当前登录用户头像。',
|
||||
])
|
||||
expect(properties(card).at(-1)?.columns).toEqual(['方法', '路径', '函数', '说明'])
|
||||
expect(card.code).toBe('router = APIRouter(tags=["用户管理"], prefix="/users")')
|
||||
expect(ROUTES.slice(card.defines?.startIndex, card.defines?.endIndex)).toBe('router')
|
||||
})
|
||||
|
||||
it('函数名带下标(⌘ + 点击跳到接口卡片);路径里的 {user_id} 各讲各的函数的参数;方法用 Swagger UI 的颜色', async () => {
|
||||
const [getUser, updateUser] = properties(await routerCard(ROUTES, 'router')).at(-1)?.items ?? []
|
||||
expect(getUser.cells[2]).toEqual([{ text: 'get_user', role: 'function', offset: ROUTES.indexOf('get_user(') }])
|
||||
const paramOf = (fn: string): number => ROUTES.indexOf('user_id: Int4Path', ROUTES.indexOf(`def ${fn}`))
|
||||
for (const [row, fn] of [[getUser, 'get_user'], [updateUser, 'update_user']] as const)
|
||||
expect(flatSegments(row.cells[1]).find(piece => piece.text === 'user_id')?.refers).toEqual({ startIndex: paramOf(fn), endIndex: paramOf(fn) + 'user_id'.length })
|
||||
expect(updateUser.cells[0]).toEqual([{ text: 'PUT', role: 'keyword', color: { dark: '#fca130', light: '#b35f00' } }])
|
||||
})
|
||||
|
||||
it('接口卡片写挂在哪个路由器上(名字带下标,⌘ + 点击跳到路由器卡片),排在说明后面', async () => {
|
||||
const rows = properties(await route(ROUTES, 'update_user'))
|
||||
expect(rows.map(row => row.label).slice(0, 4)).toEqual(['名称', '说明', '路由器', '标签'])
|
||||
expect(rows[2].value).toEqual([{ text: 'router', role: 'variable', offset: ROUTES.indexOf('router = APIRouter') }])
|
||||
})
|
||||
|
||||
it('写了依赖、默认响应才有这两行;FastAPI 应用没有前缀;变量的说明是属性 docstring;没挂接口时「0 个」、不画表格', async () => {
|
||||
const source = `${HEADER}"""整个应用。"""
|
||||
empty = APIRouter()
|
||||
|
||||
@router.get("/{item_id}")
|
||||
def show(item_id: int):
|
||||
"""看一个物品。
|
||||
|
||||
带库存。
|
||||
"""
|
||||
|
||||
@app.post("/items")
|
||||
def create():
|
||||
pass
|
||||
`
|
||||
expect(propertyLines(properties(await routerCard(source, 'router')))).toEqual([
|
||||
'名称: router',
|
||||
'前缀: /items',
|
||||
'标签: 物品',
|
||||
'依赖: verify_token',
|
||||
'默认响应: 401',
|
||||
' 401 | | 没登录',
|
||||
'接口: 1 个',
|
||||
' GET | /items/{item_id} | show | 看一个物品。',
|
||||
])
|
||||
expect(propertyLines(properties(await routerCard(source, 'app')))).toEqual([
|
||||
'名称: app',
|
||||
'说明: 整个应用。',
|
||||
'接口: 1 个',
|
||||
' POST | /items | create | ',
|
||||
])
|
||||
const empty = properties(await routerCard(source, 'empty'))
|
||||
expect(propertyLines(empty)).toEqual(['名称: empty', '接口: 0 个'])
|
||||
expect(empty.at(-1)?.columns).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('fastapi 库规则:别的文件里的定义(工作区索引 + 语言扩展查定义)', () => {
|
||||
const SCHEMAS = 'backend/src/luffy_agent/core/exceptions/schemas.py'
|
||||
const PARAMS = 'backend/src/luffy_agent/core/params.py'
|
||||
|
||||
Reference in new issue
Block a user