-
Notifications
You must be signed in to change notification settings - Fork 0
Fix plugin api not found #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1fc81d8
940761f
6c184cb
d7e3262
37212a3
0e29702
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,15 @@ def before_send(event, hint): # noqa | |
| ) | ||
|
|
||
|
|
||
| def _setup_ray_worker() -> None: # pragma: no cover | ||
| settings.server.use_ray = True | ||
|
|
||
| import framex.adapter as adapter_module | ||
|
|
||
| adapter_module._adapter = None | ||
| _setup_sentry() | ||
|
Comment on lines
+69
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mirror the Ray-mode switch in the driver process.
Suggested fix num_cpus = num_cpus if num_cpus is not None else settings.server.num_cpus
use_ray = use_ray if use_ray is not None else settings.server.use_ray
+ settings.server.use_ray = use_ray
enable_proxy = enable_proxy if enable_proxy is not None else settings.server.enable_proxyAlso applies to: 158-158 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def run( | ||
| *, | ||
| server_host: str | None = None, | ||
|
|
@@ -87,6 +96,7 @@ def run( | |
| dashboard_port = dashboard_port if dashboard_port is not None else settings.server.dashboard_port | ||
| num_cpus = num_cpus if num_cpus is not None else settings.server.num_cpus | ||
| use_ray = use_ray if use_ray is not None else settings.server.use_ray | ||
| settings.server.use_ray = use_ray | ||
| enable_proxy = enable_proxy if enable_proxy is not None else settings.server.enable_proxy | ||
| builtin_plugins = settings.load_builtin_plugins if load_builtin_plugins is None else load_builtin_plugins | ||
| external_plugins = settings.load_plugins if load_plugins is None else load_plugins | ||
|
|
@@ -146,7 +156,7 @@ def run( | |
| "env_vars": { | ||
| "REVERSION": reversion, | ||
| }, | ||
| "worker_process_setup_hook": _setup_sentry, | ||
| "worker_process_setup_hook": _setup_ray_worker, | ||
| }, | ||
| ) | ||
| serve.start( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,13 +8,20 @@ | |
| from framex.config import settings | ||
| from framex.consts import PROXY_PLUGIN_NAME | ||
| from framex.log import logger | ||
| from framex.plugin.manage import PluginManager | ||
| from framex.plugin.manage import _manager | ||
| from framex.plugin.model import Plugin, PluginApi | ||
| from framex.plugin.resolver import ( | ||
| ApiResolver, | ||
| _set_default_api_resolver, | ||
| get_current_api_resolver, | ||
| get_current_remote_apis, | ||
| get_default_api_resolver, | ||
| ) | ||
|
|
||
| C = TypeVar("C", bound=BaseModel) | ||
| _manager: PluginManager = PluginManager(silent=settings.test.silent) | ||
|
|
||
| _current_plugin: ContextVar[Optional["Plugin"]] = ContextVar("_current_plugin", default=None) | ||
| _set_default_api_resolver(ApiResolver(manager=_manager)) | ||
|
|
||
|
|
||
| def get_plugin(plugin_id: str) -> Plugin | None: | ||
|
|
@@ -40,6 +47,7 @@ def check_plugin_config_exists(plugin_name: str) -> bool: | |
| @logger.catch() | ||
| def init_all_deployments(enable_proxy: bool) -> list[Any]: | ||
| deployments = [] | ||
| all_apis = {**_manager.all_plugin_apis[ApiType.FUNC], **_manager.all_plugin_apis[ApiType.HTTP]} | ||
| for plugin in get_loaded_plugins(): | ||
| for dep in plugin.deployments: | ||
| remote_apis = { | ||
|
|
@@ -66,6 +74,7 @@ def init_all_deployments(enable_proxy: bool) -> list[Any]: | |
| deployment = get_adapter().bind( | ||
| dep.deployment, | ||
| remote_apis=remote_apis, | ||
| api_registry=all_apis, | ||
| config=plugin.config, | ||
| ) | ||
|
|
||
|
|
@@ -74,18 +83,26 @@ def init_all_deployments(enable_proxy: bool) -> list[Any]: | |
| return deployments | ||
|
|
||
|
|
||
| async def call_plugin_api( | ||
| def _resolve_plugin_api( | ||
| api_name: str | PluginApi, | ||
| interval_apis: dict[str, PluginApi] | None = None, | ||
| **kwargs: Any, | ||
| ) -> Any: | ||
| resolver: ApiResolver | None = None, | ||
| ) -> tuple[PluginApi, bool]: | ||
| current_remote_apis = get_current_remote_apis() | ||
| if isinstance(api_name, PluginApi): | ||
| api: PluginApi | None = api_name | ||
| elif isinstance(api_name, str): | ||
| api = interval_apis.get(api_name) if interval_apis else _manager.get_api(api_name) | ||
| use_proxy = False | ||
| if not api: | ||
| if isinstance(api_name, str) and api_name.startswith("/") and settings.server.enable_proxy: | ||
| return api_name, api_name.call_type == ApiType.PROXY | ||
|
|
||
|
Comment on lines
+90
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apply the Lines 91-93 return immediately for 🤖 Prompt for AI Agents |
||
| active_resolver = resolver or get_current_api_resolver() or get_default_api_resolver() | ||
| if current_remote_apis is not None: | ||
| api = active_resolver.coerce_plugin_api(current_remote_apis.get(api_name)) | ||
| else: | ||
| api = active_resolver.resolve(api_name, None) | ||
|
Comment on lines
+94
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "__init__.py" -path "*/framex/plugin/*" | head -20Repository: touale/FrameX-kit Length of output: 92 🏁 Script executed: cat -n src/framex/plugin/__init__.py | sed -n '85,110p'Repository: touale/FrameX-kit Length of output: 1260 🏁 Script executed: rg -A 3 "def get_current_api_resolver|def get_default_api_resolver" --type pyRepository: touale/FrameX-kit Length of output: 484 🏁 Script executed: rg -A 5 "def _resolve_plugin_api" src/framex/plugin/__init__.pyRepository: touale/FrameX-kit Length of output: 278 🏁 Script executed: rg -B 2 -A 10 "class ApiResolver" --type py | head -50Repository: touale/FrameX-kit Length of output: 832 🏁 Script executed: rg "_manager" src/framex/plugin/__init__.pyRepository: touale/FrameX-kit Length of output: 496 Ensure Line 94 produces Suggested fix def _resolve_plugin_api(
api_name: str | PluginApi,
resolver: ApiResolver | None = None,
) -> tuple[PluginApi, bool]:
current_remote_apis = get_current_remote_apis()
+ active_resolver = (
+ resolver
+ or get_current_api_resolver()
+ or get_default_api_resolver()
+ or ApiResolver(manager=_manager)
+ )
if isinstance(api_name, PluginApi):
return api_name, api_name.call_type == ApiType.PROXY
- active_resolver = resolver or get_current_api_resolver() or get_default_api_resolver()
if current_remote_apis is not None:
- api = active_resolver.coerce_plugin_api(current_remote_apis.get(api_name))
+ api = ApiResolver.coerce_plugin_api(current_remote_apis.get(api_name))
else:
api = active_resolver.resolve(api_name, None)🧰 Tools🪛 GitHub Actions: Test Project[error] 96-96: mypy error: Item "None" of "ApiResolver | None" has no attribute "coerce_plugin_api" [union-attr] [error] 98-98: mypy error: Item "None" of "ApiResolver | None" has no attribute "resolve" [union-attr] 🤖 Prompt for AI Agents |
||
|
|
||
| if api is None: | ||
| if current_remote_apis is not None: | ||
| raise RuntimeError( | ||
| f"API {api_name} is not declared in current plugin remote_apis; add it to required_remote_apis." | ||
| ) | ||
| if api_name.startswith("/") and settings.server.enable_proxy: | ||
| api = PluginApi( | ||
| api=api_name, | ||
| deployment_name=PROXY_PLUGIN_NAME, | ||
|
|
@@ -94,42 +111,60 @@ async def call_plugin_api( | |
| logger.opt(colors=True).warning( | ||
| f"Api(<y>{api_name}</y>) not found, use proxy plugin({PROXY_PLUGIN_NAME}) to transfer!" | ||
| ) | ||
| use_proxy = True | ||
| else: | ||
| raise RuntimeError( | ||
| f"API {api_name} is not found, please check if the plugin is loaded or the API name is correct." | ||
| ) | ||
| if api.call_type == ApiType.PROXY: | ||
| use_proxy = True | ||
|
|
||
| return api, api.call_type == ApiType.PROXY | ||
|
|
||
|
|
||
| def _normalize_plugin_call_kwargs(api: PluginApi, kwargs: dict[str, Any]) -> dict[str, Any]: | ||
| normalized_kwargs = dict(kwargs) | ||
| param_type_map = dict(api.params) | ||
| for key, val in kwargs.items(): | ||
| for key, val in normalized_kwargs.items(): | ||
| if ( | ||
| isinstance(val, dict) | ||
| and (expected_type := param_type_map.get(key)) | ||
| and isinstance(expected_type, type) | ||
| and issubclass(expected_type, BaseModel) | ||
| ): | ||
| try: | ||
| kwargs[key] = expected_type(**val) | ||
| normalized_kwargs[key] = expected_type(**val) | ||
| except Exception as e: # pragma: no cover | ||
| raise RuntimeError(f"Failed to convert '{key}' to {expected_type}") from e | ||
| result = await get_adapter().call_func(api, **kwargs) | ||
| return normalized_kwargs | ||
|
|
||
|
|
||
| def _unwrap_plugin_call_result(api_name: str | PluginApi, result: Any, use_proxy: bool) -> Any: | ||
| if isinstance(result, BaseModel): | ||
| return result.model_dump(by_alias=True) | ||
| if use_proxy: | ||
| if not isinstance(result, dict): | ||
| return result | ||
| if "status" not in result: | ||
| raise RuntimeError(f"Proxy API {api_name} returned invalid response: missing 'status' field") | ||
| res = result.get("data") | ||
| status = result.get("status") | ||
| if status not in settings.server.legal_proxy_code: | ||
| logger.opt(colors=True).error(f"<>Proxy API {api_name} call illegal: <r>{result}</r>") | ||
| raise RuntimeError(f"Proxy API {api_name} returned status {status}") | ||
| if res is None: | ||
| logger.opt(colors=True).warning(f"API {api_name} returned empty data") | ||
| return res | ||
| return result | ||
| if not use_proxy: | ||
| return result | ||
| if not isinstance(result, dict): | ||
| return result | ||
| if "status" not in result: | ||
| raise RuntimeError(f"Proxy API {api_name} returned invalid response: missing 'status' field") | ||
|
|
||
| res = result.get("data") | ||
| status = result.get("status") | ||
| if status not in settings.server.legal_proxy_code: | ||
| logger.opt(colors=True).error(f"<>Proxy API {api_name} call illegal: <r>{result}</r>") | ||
| raise RuntimeError(f"Proxy API {api_name} returned status {status}") | ||
| if res is None: | ||
| logger.opt(colors=True).warning(f"API {api_name} returned empty data") | ||
| return res | ||
|
|
||
|
|
||
| async def call_plugin_api( | ||
| api_name: str | PluginApi, | ||
| resolver: ApiResolver | None = None, | ||
| **kwargs: Any, | ||
| ) -> Any: | ||
| api, use_proxy = _resolve_plugin_api(api_name, resolver=resolver) | ||
| normalized_kwargs = _normalize_plugin_call_kwargs(api, kwargs) | ||
| result = await get_adapter().call_func(api, **normalized_kwargs) | ||
| return _unwrap_plugin_call_result(api_name, result, use_proxy) | ||
|
|
||
|
|
||
| def get_http_plugin_apis() -> list["PluginApi"]: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| from collections.abc import Mapping | ||
| from contextvars import ContextVar | ||
| from typing import Any, Protocol | ||
|
|
||
| from framex.plugin.model import PluginApi | ||
|
|
||
|
|
||
| class SupportsApiLookup(Protocol): | ||
| def get_api(self, api_name: str) -> PluginApi | None: ... | ||
|
|
||
|
|
||
| class ApiResolver: | ||
| def __init__( | ||
| self, | ||
| manager: SupportsApiLookup | None = None, | ||
| api_registry: Mapping[str, PluginApi | dict[str, Any]] | None = None, | ||
| ) -> None: | ||
| self._manager = manager | ||
| self._api_registry = api_registry or {} | ||
|
|
||
| @staticmethod | ||
| def coerce_plugin_api(api: PluginApi | dict[str, Any] | None) -> PluginApi | None: | ||
| if api is None or isinstance(api, PluginApi): | ||
| return api | ||
| if isinstance(api, dict): | ||
| return PluginApi.model_validate(api) | ||
| return None | ||
|
|
||
| def resolve( | ||
| self, | ||
| api_name: str, | ||
| api_registry: Mapping[str, PluginApi | dict[str, Any]] | None = None, | ||
| ) -> PluginApi | None: | ||
| if api_registry is not None and (api := self.coerce_plugin_api(api_registry.get(api_name))): | ||
| return api | ||
| if self._manager and (api := self._manager.get_api(api_name)): | ||
| return api | ||
| return self.coerce_plugin_api(self._api_registry.get(api_name)) | ||
|
|
||
|
|
||
| _current_api_resolver: ContextVar[ApiResolver | None] = ContextVar("_current_api_resolver", default=None) | ||
| _current_remote_apis: ContextVar[Mapping[str, PluginApi | dict[str, Any]] | None] = ContextVar( | ||
| "_current_remote_apis", default=None | ||
| ) | ||
| _default_api_resolver: ApiResolver | None = None | ||
|
|
||
|
|
||
| def get_current_api_resolver() -> ApiResolver | None: | ||
| return _current_api_resolver.get() | ||
|
|
||
|
|
||
| def get_default_api_resolver() -> ApiResolver: | ||
| if _default_api_resolver is None: | ||
| raise RuntimeError("Default API resolver is not configured") | ||
| return _default_api_resolver | ||
|
|
||
|
|
||
| def _set_default_api_resolver(resolver: ApiResolver) -> None: | ||
| global _default_api_resolver | ||
| _default_api_resolver = resolver | ||
|
|
||
|
|
||
| def set_current_api_resolver(resolver: ApiResolver | None) -> Any: | ||
| return _current_api_resolver.set(resolver) | ||
|
|
||
|
|
||
| def reset_current_api_resolver(token: Any) -> None: | ||
| _current_api_resolver.reset(token) | ||
|
|
||
|
|
||
| def get_current_remote_apis() -> Mapping[str, PluginApi | dict[str, Any]] | None: | ||
| return _current_remote_apis.get() | ||
|
|
||
|
|
||
| def set_current_remote_apis(remote_apis: Mapping[str, PluginApi | dict[str, Any]] | None) -> Any: | ||
| return _current_remote_apis.set(remote_apis) | ||
|
|
||
|
|
||
| def reset_current_remote_apis(token: Any) -> None: | ||
| _current_remote_apis.reset(token) |
Uh oh!
There was an error while loading. Please reload this page.