chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import abc
|
||||
import collections
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any
|
||||
|
||||
from ray.dashboard.optional_deps import PathLike, RouteDef, aiohttp, hdrs
|
||||
from ray.dashboard.utils import CustomEncoder, HTTPStatusCode, to_google_style
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseRouteTable(abc.ABC):
|
||||
"""A base class to bind http route to a target instance. Subclass should implement
|
||||
the _register_route method. It should define how the handler interacts with
|
||||
_BindInfo.instance.
|
||||
|
||||
Subclasses must declare their own _bind_map and _routes properties to avoid
|
||||
conflicts.
|
||||
"""
|
||||
|
||||
class _BindInfo:
|
||||
def __init__(self, filename, lineno, instance):
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.instance = instance
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def _bind_map(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def _routes(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _register_route(cls, method, path, **kwargs):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def bind(cls, instance):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def routes(cls):
|
||||
return cls._routes
|
||||
|
||||
@classmethod
|
||||
def bound_routes(cls):
|
||||
bound_items = []
|
||||
for r in cls._routes._items:
|
||||
if isinstance(r, RouteDef):
|
||||
route_method = r.handler.__route_method__
|
||||
route_path = r.handler.__route_path__
|
||||
instance = cls._bind_map[route_method][route_path].instance
|
||||
if instance is not None:
|
||||
bound_items.append(r)
|
||||
else:
|
||||
bound_items.append(r)
|
||||
routes = aiohttp.web.RouteTableDef()
|
||||
routes._items = bound_items
|
||||
return routes
|
||||
|
||||
@classmethod
|
||||
def head(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_HEAD, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_GET, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def post(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_POST, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def put(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_PUT, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def patch(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_PATCH, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_DELETE, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def view(cls, path, **kwargs):
|
||||
return cls._register_route(hdrs.METH_ANY, path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def static(cls, prefix: str, path: PathLike, **kwargs: Any) -> None:
|
||||
cls._routes.static(prefix, path, **kwargs)
|
||||
|
||||
|
||||
def method_route_table_factory():
|
||||
"""
|
||||
Return a method-based route table class, for in-process HeadModule objects.
|
||||
"""
|
||||
|
||||
class MethodRouteTable(BaseRouteTable):
|
||||
"""A helper class to bind http route to class method. Each _BindInfo.instance
|
||||
is a class instance, and for an inbound request, we invoke the async handler
|
||||
method."""
|
||||
|
||||
_bind_map = collections.defaultdict(dict)
|
||||
_routes = aiohttp.web.RouteTableDef()
|
||||
|
||||
@classmethod
|
||||
def _register_route(cls, method, path, **kwargs):
|
||||
def _wrapper(handler):
|
||||
if path in cls._bind_map[method]:
|
||||
bind_info = cls._bind_map[method][path]
|
||||
raise Exception(
|
||||
f"Duplicated route path: {path}, "
|
||||
f"previous one registered at "
|
||||
f"{bind_info.filename}:{bind_info.lineno}"
|
||||
)
|
||||
|
||||
bind_info = cls._BindInfo(
|
||||
handler.__code__.co_filename, handler.__code__.co_firstlineno, None
|
||||
)
|
||||
|
||||
@functools.wraps(handler)
|
||||
async def _handler_route(*args) -> aiohttp.web.Response:
|
||||
try:
|
||||
# Make the route handler as a bound method.
|
||||
# The args may be:
|
||||
# * (Request, )
|
||||
# * (self, Request)
|
||||
req = args[-1]
|
||||
return await handler(bind_info.instance, req)
|
||||
except Exception:
|
||||
logger.exception("Handle %s %s failed.", method, path)
|
||||
return rest_response(
|
||||
status_code=HTTPStatusCode.INTERNAL_ERROR,
|
||||
message=traceback.format_exc(),
|
||||
)
|
||||
|
||||
cls._bind_map[method][path] = bind_info
|
||||
_handler_route.__route_method__ = method
|
||||
_handler_route.__route_path__ = path
|
||||
return cls._routes.route(method, path, **kwargs)(_handler_route)
|
||||
|
||||
return _wrapper
|
||||
|
||||
@classmethod
|
||||
def bind(cls, instance):
|
||||
def predicate(o):
|
||||
if inspect.ismethod(o):
|
||||
return hasattr(o, "__route_method__") and hasattr(
|
||||
o, "__route_path__"
|
||||
)
|
||||
return False
|
||||
|
||||
handler_routes = inspect.getmembers(instance, predicate)
|
||||
for _, h in handler_routes:
|
||||
cls._bind_map[h.__func__.__route_method__][
|
||||
h.__func__.__route_path__
|
||||
].instance = instance
|
||||
|
||||
return MethodRouteTable
|
||||
|
||||
|
||||
def rest_response(
|
||||
status_code: HTTPStatusCode,
|
||||
message: str,
|
||||
convert_google_style: bool = True,
|
||||
**kwargs,
|
||||
) -> aiohttp.web.Response:
|
||||
"""
|
||||
Args:
|
||||
status_code: HTTPStatusCode
|
||||
The HTTP status code of the response.
|
||||
message: str
|
||||
The message of the response.
|
||||
convert_google_style: bool
|
||||
Whether to convert the response to google style.
|
||||
**kwargs: Additional fields included in the response ``data`` payload.
|
||||
|
||||
Returns:
|
||||
aiohttp.web.Response
|
||||
"""
|
||||
# In the dev context we allow a dev server running on a
|
||||
# different port to consume the API, meaning we need to allow
|
||||
# cross-origin access
|
||||
if os.environ.get("RAY_DASHBOARD_DEV") == "1":
|
||||
headers = {"Access-Control-Allow-Origin": "*"}
|
||||
else:
|
||||
headers = {}
|
||||
|
||||
success = status_code == HTTPStatusCode.OK
|
||||
return aiohttp.web.json_response(
|
||||
{
|
||||
"result": success,
|
||||
"msg": message,
|
||||
"data": to_google_style(kwargs) if convert_google_style else kwargs,
|
||||
},
|
||||
dumps=functools.partial(json.dumps, cls=CustomEncoder),
|
||||
headers=headers,
|
||||
status=status_code,
|
||||
)
|
||||
Reference in New Issue
Block a user