cortexgrid 0.3.17__tar.gz → 0.3.19__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/PKG-INFO +1 -1
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/serve.py +54 -7
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/pyproject.toml +1 -1
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/.gitignore +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/LICENSE +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/__init__.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/_bundle.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/_model_scheduler.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/_ray_job_driver.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/_serve_entry.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/checkpoint.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/experiment.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/infra.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/jobs.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/mlflow_util.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/__init__.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/application_spec.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/deployment_key.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/deployment_records.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/lifecycle.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/placement.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/registry_tags.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/serve_bundle.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_serving/status.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/model_storage.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/py.typed +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/ray_util.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/s3_util.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/secrets.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/cortexgrid/state.py +0 -0
- {cortexgrid-0.3.17 → cortexgrid-0.3.19}/docs/cortexgrid/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cortexgrid
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.19
|
|
4
4
|
Summary: Connect your ML code to the RoboLab compute cluster — Ray, MLflow, and S3
|
|
5
5
|
Project-URL: Homepage, https://github.com/robodatalab/cortexgrid
|
|
6
6
|
Project-URL: Repository, https://github.com/robodatalab/cortexgrid
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
async def predict(self, xs: list[float]) -> list[float]: ...
|
|
9
9
|
|
|
10
10
|
Unlike `ray.serve.ingress`, it builds the FastAPI app from the class's
|
|
11
|
-
`serve.endpoint` methods, and the class is left unwrapped: the FastAPI app
|
|
12
|
-
and the client generated for it are only recorded on it, and
|
|
11
|
+
`serve.endpoint` methods, and the class is left unwrapped: the FastAPI app,
|
|
12
|
+
its routes and the client generated for it are only recorded on it, and
|
|
13
13
|
`cortexgrid._serve_entry.build` applies Ray's ingress when it builds the Serve
|
|
14
14
|
application on the cluster.
|
|
15
15
|
|
|
@@ -24,11 +24,13 @@ locatable everywhere else (the laptop, Ray jobs, tests).
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
import inspect
|
|
27
|
+
from collections.abc import AsyncIterator
|
|
27
28
|
from dataclasses import dataclass
|
|
28
|
-
from typing import Any, Callable, TypeVar, get_type_hints
|
|
29
|
+
from typing import Any, Callable, TypeVar, get_args, get_type_hints
|
|
29
30
|
|
|
30
31
|
import httpx
|
|
31
32
|
from fastapi import FastAPI
|
|
33
|
+
from fastapi.responses import StreamingResponse
|
|
32
34
|
from pydantic import TypeAdapter
|
|
33
35
|
|
|
34
36
|
from cortexgrid.model_serving.lifecycle import Deployment, DeploymentClient
|
|
@@ -57,6 +59,10 @@ def ingress(cls: _T) -> _T:
|
|
|
57
59
|
if getattr(method, _ENDPOINT_ATTR, False):
|
|
58
60
|
marshalling = _EndpointMarshalling.of(method)
|
|
59
61
|
route = _route_of(method, marshalling)
|
|
62
|
+
route_name = f"__cortexgrid_{name}_route__"
|
|
63
|
+
route.__module__ = cls.__module__
|
|
64
|
+
route.__qualname__ = f"{cls.__qualname__}.{route_name}"
|
|
65
|
+
setattr(cls, route_name, route)
|
|
60
66
|
app.add_api_route(f"/{name}", route, methods=["POST"])
|
|
61
67
|
calls[name] = _call_of(name, method, marshalling)
|
|
62
68
|
client = type(f"{cls.__name__}Client", (_EndpointsClient,), calls)
|
|
@@ -88,7 +94,12 @@ class _EndpointMarshalling:
|
|
|
88
94
|
parameters_in_order = list(parameter_values)
|
|
89
95
|
signature_without_self = signature.replace(parameters=parameters_in_order[1:])
|
|
90
96
|
hints = get_type_hints(method)
|
|
91
|
-
|
|
97
|
+
returned_hint = hints.pop("return")
|
|
98
|
+
if inspect.isasyncgenfunction(method):
|
|
99
|
+
streamed_hints = get_args(returned_hint)
|
|
100
|
+
answer_hint = streamed_hints[0]
|
|
101
|
+
else:
|
|
102
|
+
answer_hint = returned_hint
|
|
92
103
|
answer = TypeAdapter(answer_hint)
|
|
93
104
|
parameters = {name: TypeAdapter(hint) for name, hint in hints.items()}
|
|
94
105
|
return cls(signature_without_self, parameters, answer)
|
|
@@ -113,11 +124,27 @@ class _EndpointMarshalling:
|
|
|
113
124
|
def answer_from_json(self, answered: Any) -> Any:
|
|
114
125
|
return self.answer.validate_python(answered)
|
|
115
126
|
|
|
127
|
+
def answer_to_json_line(self, answer: Any) -> bytes:
|
|
128
|
+
answered = self.answer.dump_json(answer)
|
|
129
|
+
return answered + b"\n"
|
|
130
|
+
|
|
131
|
+
def answer_from_json_line(self, line: str) -> Any:
|
|
132
|
+
return self.answer.validate_json(line)
|
|
133
|
+
|
|
116
134
|
|
|
117
135
|
def _route_of(
|
|
118
136
|
method: Callable[..., Any], marshalling: _EndpointMarshalling
|
|
119
137
|
) -> Callable[..., Any]:
|
|
120
|
-
if inspect.
|
|
138
|
+
if inspect.isasyncgenfunction(method):
|
|
139
|
+
|
|
140
|
+
async def route(self: Any, body: dict[str, Any]) -> Any:
|
|
141
|
+
arguments = marshalling.arguments_from_json(body)
|
|
142
|
+
answers = method(self, **arguments)
|
|
143
|
+
lines = _json_lines_of(answers, marshalling)
|
|
144
|
+
streamed = StreamingResponse(lines, media_type="application/x-ndjson")
|
|
145
|
+
return streamed
|
|
146
|
+
|
|
147
|
+
elif inspect.iscoroutinefunction(method):
|
|
121
148
|
|
|
122
149
|
async def route(self: Any, body: dict[str, Any]) -> Any:
|
|
123
150
|
arguments = marshalling.arguments_from_json(body)
|
|
@@ -134,14 +161,34 @@ def _route_of(
|
|
|
134
161
|
return answered
|
|
135
162
|
|
|
136
163
|
route.__name__ = method.__name__
|
|
137
|
-
route.__qualname__ = method.__qualname__
|
|
138
164
|
return route
|
|
139
165
|
|
|
140
166
|
|
|
167
|
+
async def _json_lines_of(
|
|
168
|
+
answers: AsyncIterator[Any], marshalling: _EndpointMarshalling
|
|
169
|
+
) -> AsyncIterator[bytes]:
|
|
170
|
+
async for answer in answers:
|
|
171
|
+
line = marshalling.answer_to_json_line(answer)
|
|
172
|
+
yield line
|
|
173
|
+
|
|
174
|
+
|
|
141
175
|
def _call_of(
|
|
142
176
|
name: str, method: Callable[..., Any], marshalling: _EndpointMarshalling
|
|
143
177
|
) -> Callable[..., Any]:
|
|
144
|
-
if inspect.
|
|
178
|
+
if inspect.isasyncgenfunction(method):
|
|
179
|
+
|
|
180
|
+
async def call(self: _EndpointsClient, *args: Any, **kwargs: Any) -> Any:
|
|
181
|
+
body = marshalling.arguments_to_json(*args, **kwargs)
|
|
182
|
+
async with httpx.AsyncClient(timeout=None) as client:
|
|
183
|
+
async with client.stream(
|
|
184
|
+
"POST", f"{self.url}/{name}", json=body
|
|
185
|
+
) as response:
|
|
186
|
+
response.raise_for_status()
|
|
187
|
+
async for line in response.aiter_lines():
|
|
188
|
+
answer = marshalling.answer_from_json_line(line)
|
|
189
|
+
yield answer
|
|
190
|
+
|
|
191
|
+
elif inspect.iscoroutinefunction(method):
|
|
145
192
|
|
|
146
193
|
async def call(self: _EndpointsClient, *args: Any, **kwargs: Any) -> Any:
|
|
147
194
|
body = marshalling.arguments_to_json(*args, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|