Skip to content

Commit

Permalink
Adjust http endpoints and naming
Browse files Browse the repository at this point in the history
  • Loading branch information
oeway committed Aug 8, 2024
1 parent cdfa7fe commit ba5e616
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 153 deletions.
132 changes: 6 additions & 126 deletions hypha/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,78 +16,6 @@
from hypha.utils import GzipRoute
from hypha import __version__ as VERSION

SERVICES_OPENAPI_SCHEMA = {
"openapi": "3.1.0",
"info": {
"title": "Hypha Services",
"description": "Providing access to services in Hypha",
"version": f"v{VERSION}",
},
"paths": {
"/call": {
"post": {
"description": "Call a service function",
"operationId": "CallServiceFunction",
"requestBody": {
"required": True,
"description": "The request body type depends on each service function schema",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServiceFunctionSchema"
}
}
},
},
"deprecated": False,
}
},
"/list": {
"get": {
"description": "List services under a workspace",
"operationId": "ListServices",
"parameters": [
{
"name": "workspace",
"in": "query",
"description": "Workspace name",
"required": True,
"schema": {"type": "string"},
}
],
"deprecated": False,
}
},
},
"components": {
"schemas": {
"ServiceFunctionSchema": {
"type": "object",
"properties": {
"workspace": {
"description": "Workspace name, optional if the service_id contains workspace name",
"type": "string",
},
"service_id": {
"description": "Service ID, format: workspace/client_id:service_id",
"type": "string",
},
"function_key": {
"description": "Function key, can contain dot to refer to deeper object",
"type": "string",
},
"function_kwargs": {
"description": "Function keyword arguments, must be set according to the function schema",
"type": "object",
},
},
"required": ["service_id", "function_key"],
}
}
},
}


class MsgpackResponse(Response):
"""Response class for msgpack encoding."""

Expand Down Expand Up @@ -283,7 +211,7 @@ async def list_all_workspaces(
content={"success": False, "detail": str(exp)},
)

@router.get("/{workspace}/info")
@router.get("/workspaces/{workspace}")
async def get_workspace_info(
workspace: str,
user_info: login_optional = Depends(login_optional),
Expand Down Expand Up @@ -325,35 +253,7 @@ async def list_services(
"""List services under a workspace."""
return await get_workspace_services(workspace, user_info)

@router.get("/services/{service_id:path}")
async def get_service_info(
service_id: str,
user_info: login_optional = Depends(login_optional),
):
"""Get service info."""
assert "/" in service_id, "service_id should contain workspace name"
assert ":" in service_id, "service_id should contain client_id"
workspace, service_id = service_id.split("/")
return await get_service_info(workspace, service_id, user_info)

@router.post("/services/{workspace}/{service_id}/{function_key}")
async def call_service_function_post(
workspace: str,
service_id: str,
function_key: str,
request: Request,
user_info: login_optional = Depends(login_optional),
):
"""Call a service function by keys."""
function_kwargs = await extracted_kwargs(request, use_function_kwargs=False)
response_type = detected_response_type(request)
return await service_function(
(workspace, service_id, function_key),
function_kwargs,
response_type,
user_info,
)

@router.get("/services//{workspace}")
@router.get("/{workspace}/services")
async def get_workspace_services(
workspace: str,
Expand All @@ -379,6 +279,7 @@ async def get_workspace_services(
content={"success": False, "detail": str(exp)},
)

@router.get("/services/{workspace}/{service_id}")
@router.get("/{workspace}/services/{service_id}")
async def get_service_info(
workspace: str,
Expand Down Expand Up @@ -412,32 +313,11 @@ async def _call_service_function(func, kwargs):
results = _rpc.encode(results)
return results

@router.get("/services/{workspace}/{service_id}/{function_key}")
@router.post("/services/{workspace}/{service_id}/{function_key}")
@router.get("/{workspace}/services/{service_id}/{function_key}")
async def service_function_get(
workspace: str,
service_id: str,
function_key: str,
request: Request,
user_info: login_optional = Depends(login_optional),
):
"""Run service function by keys."""
function_kwargs = await extracted_kwargs(request, use_function_kwargs=False)
response_type = detected_response_type(request)
try:
return await service_function(
(workspace, service_id, function_key),
function_kwargs,
response_type,
user_info,
)
except Exception:
return JSONResponse(
status_code=400,
content={"success": False, "detail": traceback.format_exc()},
)

@router.post("/{workspace}/services/{service_id}/{function_key}")
async def service_function_post(
async def call_service_function(
workspace: str,
service_id: str,
function_key: str,
Expand Down
50 changes: 25 additions & 25 deletions hypha/runner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@


logging.basicConfig(stream=sys.stdout)
logger = logging.getLogger("browser-runner")
logger = logging.getLogger("app-runner")
logger.setLevel(logging.INFO)


async def export_service(plugin_api, config, hypha_rpc):
async def export_service(app_api, config, hypha_rpc):
try:
wm = await connect_to_server(config)
hypha_rpc.api.update(wm) # make the api available to the app
rpc = wm.rpc
if not isinstance(plugin_api, dict) and inspect.isclass(type(plugin_api)):
plugin_api = {a: getattr(plugin_api, a) for a in dir(plugin_api)}
if not isinstance(app_api, dict) and inspect.isclass(type(app_api)):
app_api = {a: getattr(app_api, a) for a in dir(app_api)}
# Copy the app name as the default name
plugin_api["id"] = "default"
plugin_api["name"] = config.get("name", "default")
svc = await rpc.register_service(plugin_api, overwrite=True, notify=True)
app_api["id"] = "default"
app_api["name"] = config.get("name", "default")
svc = await rpc.register_service(app_api, overwrite=True, notify=True)
svc = await rpc.get_remote_service(svc["id"])
if svc.setup:
await svc.setup()
Expand All @@ -53,26 +53,26 @@ def export(api, config=None):
return hypha_rpc


async def run_plugin(plugin_file, default_config, quit_on_ready=False):
async def run_app(app_file, default_config, quit_on_ready=False):
"""Load app file."""
loop = asyncio.get_event_loop()
if os.path.isfile(plugin_file):
async with aiofiles.open(plugin_file, "r", encoding="utf-8") as fil:
if os.path.isfile(app_file):
async with aiofiles.open(app_file, "r", encoding="utf-8") as fil:
content = await fil.read()
elif plugin_file.startswith("http"):
with urllib.request.urlopen(plugin_file) as response:
elif app_file.startswith("http"):
with urllib.request.urlopen(app_file) as response:
content = response.read().decode("utf-8")
# remove query string
plugin_file = plugin_file.split("?")[0]
app_file = app_file.split("?")[0]
else:
raise Exception(f"Invalid input app file path: {plugin_file}")
raise Exception(f"Invalid input app file path: {app_file}")

if plugin_file.endswith(".py"):
filename, _ = os.path.splitext(os.path.basename(plugin_file))
if app_file.endswith(".py"):
filename, _ = os.path.splitext(os.path.basename(app_file))
default_config["name"] = filename[:32]
hypha_rpc = await patch_hypha_rpc(default_config)
exec(content, globals()) # pylint: disable=exec-used
logger.info("Plugin executed")
logger.info("app executed")

if quit_on_ready:

Expand All @@ -84,28 +84,28 @@ def done_callback(fut):

hypha_rpc.ready.add_done_callback(done_callback)

elif plugin_file.endswith(".imjoy.html"):
elif app_file.endswith(".imjoy.html"):
# load config
found = re.findall("<config (.*)>\n(.*)</config>", content, re.DOTALL)[0]
if "json" in found[0]:
plugin_config = json.loads(found[1])
app_config = json.loads(found[1])
elif "yaml" in found[0]:
plugin_config = yaml.safe_load(found[1])
default_config.update(plugin_config)
app_config = yaml.safe_load(found[1])
default_config.update(app_config)
hypha_rpc = await patch_hypha_rpc(default_config)
# load script
found = re.findall("<script (.*)>\n(.*)</script>", content, re.DOTALL)[0]
if "python" in found[0]:
exec(found[1], globals()) # pylint: disable=exec-used
logger.info("Plugin executed")
logger.info("app executed")
if quit_on_ready:
hypha_rpc.ready.add_done_callback(lambda fut: loop.stop())
else:
raise RuntimeError(
f"Invalid script type ({found[0]}) in file {plugin_file}"
f"Invalid script type ({found[0]}) in file {app_file}"
)
else:
raise RuntimeError(f"Invalid script file type ({plugin_file})")
raise RuntimeError(f"Invalid script file type ({app_file})")


async def start(args):
Expand All @@ -116,7 +116,7 @@ async def start(args):
"workspace": args.workspace,
"token": args.token,
}
await run_plugin(args.file, default_config, quit_on_ready=args.quit_on_ready)
await run_app(args.file, default_config, quit_on_ready=args.quit_on_ready)
except Exception: # pylint: disable=broad-except
logger.exception("Failed to run app, exiting.")
loop = asyncio.get_event_loop()
Expand Down
2 changes: 1 addition & 1 deletion tests/example_service_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async def start_service(server_url, service_id, workspace=None, token=None):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Test services")
parser.add_argument(
"--server-url", type=str, default="https://ai.imjoy.io/", help="The server url"
"--server-url", type=str, default="https://ai.imjoy.io", help="The server url"
)
parser.add_argument(
"--service-id", type=str, default="test-service", help="The service id"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ async def test_http_proxy(minio_server, fastapi_server, test_user_token):
assert workspace in response

response = requests.get(
f"{SERVER_URL}/{workspace}/info", headers={"Authorization": f"Bearer {token}"}
f"{SERVER_URL}/workspaces/{workspace}", headers={"Authorization": f"Bearer {token}"}
)
assert response.ok, response.json()["detail"]
response = response.json()
Expand Down

0 comments on commit ba5e616

Please sign in to comment.