Skip to content

Commit d7f98dc

Browse files
author
touale
committed
feat: add plugin description and tags metadata
1 parent d08d616 commit d7f98dc

7 files changed

Lines changed: 72 additions & 10 deletions

File tree

src/framex/driver/application.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ async def _on_start(deployment: Any) -> None:
9595
redirect_slashes=False,
9696
)
9797

98+
application.state.tags_metadata_map = []
99+
98100
if settings.auth.oauth:
99101
application.add_api_route(
100102
settings.auth.oauth.redirect_uri,
@@ -118,6 +120,7 @@ async def get_open_api_endpoint(_: Annotated[str, Depends(authenticate)]) -> dic
118120
version=VERSION,
119121
description=build_openapi_description(),
120122
routes=application.routes,
123+
tags=application.state.tags_metadata_map,
121124
)
122125

123126
@application.exception_handler(HTTPException)

src/framex/driver/ingress.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def __init__(self, deployments: list[Any], plugin_apis: list["PluginApi"]) -> No
6060
stream=plugin_api.stream,
6161
direct_output=plugin_api.raw_response,
6262
tags=plugin_api.tags,
63+
description=plugin_api.description,
6364
**plugin_api.extend_kwargs,
6465
)
6566

@@ -73,6 +74,7 @@ def register_route(
7374
stream: bool = False,
7475
direct_output: bool = False,
7576
tags: list[str] | None = None,
77+
description: str | None = None,
7678
auth_keys: list[str] | None = None,
7779
include_in_schema: bool = True,
7880
**kwargs: Any,
@@ -161,6 +163,7 @@ def _verify_api_key(request: Request, api_key: str | None = Depends(api_key_head
161163
response_class=StreamingResponse if stream else JSONResponse,
162164
dependencies=dependencies,
163165
include_in_schema=include_in_schema,
166+
description=description,
164167
**kwargs,
165168
)
166169
methods_str = ",".join(m.upper() for m in methods)
@@ -188,6 +191,9 @@ def add_api_route(
188191
endpoint: Callable[..., Any],
189192
*,
190193
methods: list[str] | None = None,
194+
tags: list[str] | None = None,
195+
description: str | None = None,
196+
include_in_schema: bool = True,
191197
**kwargs: Any,
192198
) -> None:
193199
method_set: set[str] = {m.upper() for m in methods} if methods else {"GET"}
@@ -201,4 +207,22 @@ def add_api_route(
201207
):
202208
raise RuntimeError(f"Duplicate API route: {sorted(method_set)} {norm_path}")
203209

204-
app.add_api_route(path, endpoint, methods=list(method_set), **kwargs)
210+
app.add_api_route(
211+
path,
212+
endpoint,
213+
methods=list(method_set), # type: ignore
214+
tags=tags, # type: ignore
215+
include_in_schema=include_in_schema,
216+
**kwargs,
217+
)
218+
219+
if include_in_schema and tags:
220+
names = list({tag["name"] for tag in app.state.tags_metadata_map})
221+
for tag in tags:
222+
if tag not in names:
223+
app.state.tags_metadata_map.append(
224+
{
225+
"name": tag,
226+
"description": description,
227+
},
228+
)

src/framex/plugin/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,17 @@ async def _call_remote_api(self, api_name: str, **kwargs: Any) -> Any:
7373

7474
def _post_call_remote_api_hook(self, data: Any) -> Any:
7575
return data
76+
77+
78+
def build_plugin_description(
79+
author: str,
80+
version: str,
81+
description: str,
82+
repo: str,
83+
) -> str:
84+
return (
85+
f"**Author**: {author}\n\n"
86+
f"**Version**: {version}\n\n"
87+
f"**Description**: {description}\n\n"
88+
f"**Repo**: [{repo}]({repo})"
89+
)

src/framex/plugin/model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class PluginApi(BaseModel):
3434
params: list[tuple[str, type[Any] | Callable[..., Any]]] = Field(default_factory=list)
3535
call_type: ApiType = ApiType.HTTP
3636
tags: list[str] | None = None
37+
description: str | None = None
3738
stream: bool = False
3839
raw_response: bool = False
3940
extend_kwargs: dict[str, Any] = Field(default_factory=dict)

src/framex/plugin/on.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from framex.adapter import get_adapter
1111
from framex.consts import API_STR, PROXY_PLUGIN_NAME
12+
from framex.plugin.base import build_plugin_description
1213
from framex.plugin.model import ApiType, PluginApi, PluginDeployment
1314
from framex.utils import cache_decode, cache_encode, extract_method_params, plugin_to_deployment_name
1415

@@ -42,11 +43,16 @@ def decorator(cls: type) -> type:
4243
if func.__tags:
4344
tags: list[str] = func.__tags
4445
else:
45-
author: str = plugin.module.__plugin_meta__.author
46-
description = plugin.module.__plugin_meta__.description
47-
version: str = plugin.module.__plugin_meta__.version
48-
version = f"v{version}" if not version.startswith("v") else version
49-
tags = [f"{plugin.name} ({version}) by {author}: {description}"]
46+
tags = [plugin.name]
47+
48+
version: str = plugin.module.__plugin_meta__.version
49+
version = f"v{version}" if not version.startswith("v") else version
50+
description = build_plugin_description(
51+
plugin.module.__plugin_meta__.author,
52+
version,
53+
plugin.module.__plugin_meta__.description,
54+
plugin.module.__plugin_meta__.url,
55+
)
5056

5157
plugin_apis.append(
5258
PluginApi(
@@ -57,6 +63,7 @@ def decorator(cls: type) -> type:
5763
params=params,
5864
call_type=call_type,
5965
tags=tags,
66+
description=description,
6067
stream=func.__expose_stream,
6168
raw_response=raw_response,
6269
extend_kwargs=func.__kwargs,

src/framex/plugins/proxy/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from framex.consts import BACKEND_NAME, PROXY_FUNC_HTTP_PATH, PROXY_PLUGIN_NAME
1414
from framex.log import logger
1515
from framex.plugin import BasePlugin, PluginApi, PluginMetadata, on_register
16+
from framex.plugin.base import build_plugin_description
1617
from framex.plugin.model import ApiType
1718
from framex.plugin.on import on_request
1819
from framex.plugins.proxy.builder import (
@@ -30,8 +31,7 @@
3031
__plugin_meta__ = PluginMetadata(
3132
name="proxy",
3233
version=VERSION,
33-
description="一个特殊的 framx proxy 插件, 充当透明代理。"
34-
"它接收 API 请求并将其转发到已配置的外部 HTTP 端点,并将响应返回给调用者。",
34+
description="proxy 是 FrameX 的核心系统插件, 作为透明代理转发 API 请求至外部 HTTP 端点并返回响应。",
3535
author="touale",
3636
url="https://github.com/touale/FrameX-kit",
3737
required_remote_apis=[],
@@ -170,6 +170,12 @@ async def _parse_openai_docs(self, url: str) -> None:
170170
func_name="register_route",
171171
)
172172
handle = adapter.get_handle(PROXY_PLUGIN_NAME)
173+
description = build_plugin_description(
174+
__plugin_meta__.author,
175+
f"v{__plugin_meta__.version}",
176+
__plugin_meta__.description,
177+
__plugin_meta__.url,
178+
)
173179
await adapter.call_func(
174180
plugin_api,
175181
path=path,
@@ -179,7 +185,8 @@ async def _parse_openai_docs(self, url: str) -> None:
179185
handle=handle,
180186
stream=is_stream,
181187
direct_output=True,
182-
tags=[f"{__plugin_meta__.name} (v{__plugin_meta__.version}) by {__plugin_meta__.author} [{url}]"],
188+
tags=[f"{__plugin_meta__.name}({url})"],
189+
description=description,
183190
)
184191

185192
# Proxy api to map

tests/driver/test_ingress.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ def test_add_first_route_success(ingress, mock_app):
4040

4141
ingress.add_api_route("/users", endpoint, methods=["GET"])
4242

43-
mock_app.add_api_route.assert_called_once_with("/users", endpoint, methods=["GET"])
43+
mock_app.add_api_route.assert_called_once_with(
44+
"/users",
45+
endpoint,
46+
methods=["GET"],
47+
tags=None,
48+
include_in_schema=True,
49+
)
4450

4551

4652
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)