web_python
This commit is contained in:
parent
03beb6970c
commit
e8868b636f
9 changed files with 680 additions and 0 deletions
3
web_python/make.sh
Executable file
3
web_python/make.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
python setup.py sdist bdist_wheel
|
||||
cp ./dist/ray_web-0.0.1-py3-none-any.whl ../web
|
||||
102
web_python/ray/__init__.py
Normal file
102
web_python/ray/__init__.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
from ray.api import ClientAPI
|
||||
from ray.api import APIImpl
|
||||
from typing import Optional, List, Tuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# _client_api has to be external to the API stub, below.
|
||||
# Otherwise, ray.remote() that contains ray.remote()
|
||||
# contains a reference to the RayAPIStub, therefore a
|
||||
# reference to the _client_api, and then tries to pickle
|
||||
# the thing.
|
||||
_client_api: Optional[APIImpl] = None
|
||||
|
||||
_server_api: Optional[APIImpl] = None
|
||||
|
||||
_is_server: bool = False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def stash_api_for_tests(in_test: bool):
|
||||
global _is_server
|
||||
is_server = _is_server
|
||||
if in_test:
|
||||
_is_server = True
|
||||
yield _server_api
|
||||
if in_test:
|
||||
_is_server = is_server
|
||||
|
||||
|
||||
def _set_client_api(val: Optional[APIImpl]):
|
||||
global _client_api
|
||||
global _is_server
|
||||
if _client_api is not None:
|
||||
raise Exception("Trying to set more than one client API")
|
||||
_client_api = val
|
||||
_is_server = False
|
||||
|
||||
|
||||
def _set_server_api(val: Optional[APIImpl]):
|
||||
global _server_api
|
||||
global _is_server
|
||||
if _server_api is not None:
|
||||
raise Exception("Trying to set more than one server API")
|
||||
_server_api = val
|
||||
_is_server = True
|
||||
|
||||
|
||||
def reset_api():
|
||||
global _client_api
|
||||
global _server_api
|
||||
global _is_server
|
||||
_client_api = None
|
||||
_server_api = None
|
||||
_is_server = False
|
||||
|
||||
|
||||
def _get_client_api() -> APIImpl:
|
||||
global _client_api
|
||||
global _server_api
|
||||
global _is_server
|
||||
api = None
|
||||
if _is_server:
|
||||
api = _server_api
|
||||
else:
|
||||
api = _client_api
|
||||
if api is None:
|
||||
# We're inside a raylet worker
|
||||
raise Exception("CoreRayAPI not supported")
|
||||
return api
|
||||
|
||||
|
||||
class RayAPIStub:
|
||||
def connect(self):
|
||||
from ray.worker import Worker
|
||||
_client_worker = Worker()
|
||||
_set_client_api(ClientAPI(_client_worker))
|
||||
|
||||
def disconnect(self):
|
||||
global _client_api
|
||||
if _client_api is not None:
|
||||
_client_api.close()
|
||||
_client_api = None
|
||||
|
||||
def __getattr__(self, key: str):
|
||||
global _get_client_api
|
||||
api = _get_client_api()
|
||||
return getattr(api, key)
|
||||
|
||||
|
||||
ray = RayAPIStub()
|
||||
|
||||
# Someday we might add methods in this module so that someone who
|
||||
# tries to `import ray_client as ray` -- as a module, instead of
|
||||
# `from ray_client import ray` -- as the API stub
|
||||
# still gets expected functionality. This is the way the ray package
|
||||
# worked in the past.
|
||||
#
|
||||
# This really calls for PEP 562: https://www.python.org/dev/peps/pep-0562/
|
||||
# But until Python 3.6 is EOL, here we are.
|
||||
77
web_python/ray/api.py
Normal file
77
web_python/ray/api.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# This file defines an interface and client-side API stub
|
||||
# for referring either to the core Ray API or the same interface
|
||||
# from the Ray client.
|
||||
#
|
||||
# In tandem with __init__.py, we want to expose an API that's
|
||||
# close to `python/ray/__init__.py` but with more than one implementation.
|
||||
# The stubs in __init__ should call into a well-defined interface.
|
||||
# Only the core Ray API implementation should actually `import ray`
|
||||
# (and thus import all the raylet worker C bindings and such).
|
||||
# But to make sure that we're matching these calls, we define this API.
|
||||
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class APIImpl(ABC):
|
||||
@abstractmethod
|
||||
def get(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def put(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def wait(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remote(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def call_remote(self, instance, kind: int, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_actor_from_object(self, id):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class ClientAPI(APIImpl):
|
||||
def __init__(self, worker):
|
||||
self.worker = worker
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.worker.get(*args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
return self.worker.put(*args, **kwargs)
|
||||
|
||||
def wait(self, *args, **kwargs):
|
||||
return self.worker.wait(*args, **kwargs)
|
||||
|
||||
def remote(self, *args, **kwargs):
|
||||
return self.worker.remote(*args, **kwargs)
|
||||
|
||||
def call_remote(self, f, kind, *args, **kwargs):
|
||||
return self.worker.call_remote(f, kind, *args, **kwargs)
|
||||
|
||||
def get_actor_from_object(self, id):
|
||||
raise Exception("Calling get_actor_from_object on the client side")
|
||||
|
||||
def close(self, *args, **kwargs):
|
||||
return self.worker.close()
|
||||
|
||||
def __getattr__(self, key: str):
|
||||
if not key.startswith("_"):
|
||||
raise NotImplementedError(
|
||||
"Not available in Ray client: `ray.{}`. This method is only "
|
||||
"available within Ray remote functions and is not yet "
|
||||
"implemented in the client API.".format(key))
|
||||
return self.__getattribute__(key)
|
||||
90
web_python/ray/client_app.py
Normal file
90
web_python/ray/client_app.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from ray import ray
|
||||
from typing import Tuple
|
||||
|
||||
ray.connect("localhost:50051")
|
||||
|
||||
|
||||
@ray.remote
|
||||
class HelloActor:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
def say_hello(self, whom: str) -> Tuple[str, int]:
|
||||
self.count += 1
|
||||
return ("Hello " + whom, self.count)
|
||||
|
||||
|
||||
actor = HelloActor.remote()
|
||||
s, count = ray.get(actor.say_hello.remote("you"))
|
||||
print(s, count)
|
||||
assert s == "Hello you"
|
||||
assert count == 1
|
||||
s, count = ray.get(actor.say_hello.remote("world"))
|
||||
print(s, count)
|
||||
assert s == "Hello world"
|
||||
assert count == 2
|
||||
|
||||
|
||||
@ray.remote
|
||||
def plus2(x):
|
||||
return x + 2
|
||||
|
||||
|
||||
@ray.remote
|
||||
def fact(x):
|
||||
print(x, type(fact))
|
||||
if x <= 0:
|
||||
return 1
|
||||
# This hits the "nested tasks" issue
|
||||
# https://github.com/ray-project/ray/issues/3644
|
||||
# So we're on the right track!
|
||||
return ray.get(fact.remote(x - 1)) * x
|
||||
|
||||
|
||||
@ray.remote
|
||||
def get_nodes():
|
||||
return ray.nodes() # Can access the full Ray API in remote methods.
|
||||
|
||||
|
||||
print("Cluster nodes", ray.get(get_nodes.remote()))
|
||||
print(ray.nodes())
|
||||
|
||||
objectref = ray.put("hello world")
|
||||
|
||||
# `ClientObjectRef(...)`
|
||||
print(objectref)
|
||||
|
||||
# `hello world`
|
||||
print(ray.get(objectref))
|
||||
|
||||
ref2 = plus2.remote(234)
|
||||
# `ClientObjectRef(...)`
|
||||
print(ref2)
|
||||
# `236`
|
||||
print(ray.get(ref2))
|
||||
|
||||
ref3 = fact.remote(20)
|
||||
# `ClientObjectRef(...)`
|
||||
print(ref3)
|
||||
# `2432902008176640000`
|
||||
print(ray.get(ref3))
|
||||
|
||||
# Reuse the cached ClientRemoteFunc object
|
||||
ref4 = fact.remote(5)
|
||||
# `120`
|
||||
print(ray.get(ref4))
|
||||
|
||||
ref5 = fact.remote(10)
|
||||
|
||||
print([ref2, ref3, ref4, ref5])
|
||||
# should return ref2, ref3, ref4
|
||||
res = ray.wait([ref5, ref2, ref3, ref4], num_returns=3)
|
||||
print(res)
|
||||
assert [ref2, ref3, ref4] == res[0]
|
||||
assert [ref5] == res[1]
|
||||
|
||||
# should return ref2, ref3, ref4, ref5
|
||||
res = ray.wait([ref2, ref3, ref4, ref5], num_returns=4)
|
||||
print(res)
|
||||
assert [ref2, ref3, ref4, ref5] == res[0]
|
||||
assert [] == res[1]
|
||||
204
web_python/ray/common.py
Normal file
204
web_python/ray/common.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import ray.webpb as webpb
|
||||
from ray import ray
|
||||
from typing import Dict
|
||||
import cloudpickle
|
||||
|
||||
|
||||
class ClientBaseRef:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (
|
||||
type(self).__name__,
|
||||
self.id.hex(),
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def binary(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class ClientObjectRef(ClientBaseRef):
|
||||
pass
|
||||
|
||||
|
||||
class ClientActorNameRef(ClientBaseRef):
|
||||
pass
|
||||
|
||||
|
||||
class ClientRemoteFunc:
|
||||
def __init__(self, f):
|
||||
self._func = f
|
||||
self._name = f.__name__
|
||||
self.id = None
|
||||
self._ref = None
|
||||
self._raylet_remote = None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise TypeError(f"Remote function cannot be called directly. "
|
||||
"Use {self._name}.remote method instead")
|
||||
|
||||
def remote(self, *args, **kwargs):
|
||||
return ray.call_remote(self, webpb.ClientTaskType.FUNCTION, *args,
|
||||
**kwargs)
|
||||
|
||||
def _get_ray_remote_impl(self):
|
||||
if self._raylet_remote is None:
|
||||
self._raylet_remote = ray.remote(self._func)
|
||||
return self._raylet_remote
|
||||
|
||||
def __repr__(self):
|
||||
return "ClientRemoteFunc(%s, %s)" % (self._name, self.id)
|
||||
|
||||
def _prepare_client_task(self) -> webpb.ClientTask:
|
||||
if self._ref is None:
|
||||
self._ref = ray.put(self._func)
|
||||
task = webpb.ClientTask()
|
||||
task.type = webpb.ClientTaskType.FUNCTION
|
||||
task.name = self._name
|
||||
task.payload_id = self._ref.id
|
||||
return task
|
||||
|
||||
|
||||
class ClientActorClass:
|
||||
def __init__(self, actor_cls):
|
||||
self.actor_cls = actor_cls
|
||||
self._name = actor_cls.__name__
|
||||
self._ref = None
|
||||
self._raylet_remote = None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise TypeError(f"Remote actor cannot be instantiated directly. "
|
||||
"Use {self._name}.remote() instead")
|
||||
|
||||
def __getstate__(self) -> Dict:
|
||||
state = {
|
||||
"actor_cls": self.actor_cls,
|
||||
"_name": self._name,
|
||||
"_ref": self._ref,
|
||||
}
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: Dict) -> None:
|
||||
self.actor_cls = state["actor_cls"]
|
||||
self._name = state["_name"]
|
||||
self._ref = state["_ref"]
|
||||
|
||||
def remote(self, *args, **kwargs):
|
||||
# Actually instantiate the actor
|
||||
ref = ray.call_remote(self, webpb.ClientTaskType.ACTOR, *args,
|
||||
**kwargs)
|
||||
return ClientActorHandle(ClientActorNameRef(ref.id), self)
|
||||
|
||||
def __repr__(self):
|
||||
return "ClientRemoteActor(%s, %s)" % (self._name, self._ref)
|
||||
|
||||
def __getattr__(self, key):
|
||||
raise NotImplementedError("static methods")
|
||||
|
||||
def _prepare_client_task(self):
|
||||
if self._ref is None:
|
||||
self._ref = ray.put(self.actor_cls)
|
||||
task = webpb.ClientTask()
|
||||
task.type = webpb.ClientTaskType.ACTOR
|
||||
task.name = self._name
|
||||
task.payload_id = self._ref.id
|
||||
return task
|
||||
|
||||
|
||||
class ClientActorHandle:
|
||||
def __init__(self, actor_id: ClientActorNameRef,
|
||||
actor_class: ClientActorClass):
|
||||
self.actor_id = actor_id
|
||||
self.actor_class = actor_class
|
||||
self._real_actor_handle = None
|
||||
|
||||
def _get_ray_remote_impl(self):
|
||||
if self._real_actor_handle is None:
|
||||
self._real_actor_handle = ray.get_actor_from_object(self.actor_id)
|
||||
return self._real_actor_handle
|
||||
|
||||
def __getstate__(self) -> Dict:
|
||||
state = {
|
||||
"actor_id": self.actor_id,
|
||||
"actor_class": self.actor_class,
|
||||
"_real_actor_handle": self._real_actor_handle,
|
||||
}
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: Dict) -> None:
|
||||
self.actor_id = state["actor_id"]
|
||||
self.actor_class = state["actor_class"]
|
||||
self._real_actor_handle = state["_real_actor_handle"]
|
||||
|
||||
def __getattr__(self, key):
|
||||
return ClientRemoteMethod(self, key)
|
||||
|
||||
def __repr__(self):
|
||||
return "ClientActorHandle(%s, %s, %s)" % (
|
||||
self.actor_id, self.actor_class, self._real_actor_handle)
|
||||
|
||||
|
||||
class ClientRemoteMethod:
|
||||
def __init__(self, actor_handle: ClientActorHandle, method_name: str):
|
||||
self.actor_handle = actor_handle
|
||||
self.method_name = method_name
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise TypeError(f"Remote method cannot be called directly. "
|
||||
"Use {self._name}.remote() instead")
|
||||
|
||||
def _get_ray_remote_impl(self):
|
||||
return getattr(self.actor_handle._get_ray_remote_impl(),
|
||||
self.method_name)
|
||||
|
||||
def __getstate__(self) -> Dict:
|
||||
state = {
|
||||
"actor_handle": self.actor_handle,
|
||||
"method_name": self.method_name,
|
||||
}
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: Dict) -> None:
|
||||
self.actor_handle = state["actor_handle"]
|
||||
self.method_name = state["method_name"]
|
||||
|
||||
def remote(self, *args, **kwargs):
|
||||
return ray.call_remote(self, webpb.ClientTaskType.METHOD, *args,
|
||||
**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
name = "%s.%s" % (self.actor_handle.actor_class._name,
|
||||
self.method_name)
|
||||
return "ClientRemoteMethod(%s, %s)" % (name,
|
||||
self.actor_handle.actor_id)
|
||||
|
||||
def _prepare_client_task(self):
|
||||
task = webpb.ClientTask()
|
||||
task.type = webpb.ClientTask.METHOD
|
||||
task.name = self.method_name
|
||||
task.payload_id = self.actor_handle.actor_id.id
|
||||
return task
|
||||
|
||||
|
||||
# def convert_from_arg(pb) -> Any:
|
||||
# if pb.local == webpb.Locality.REFERENCE:
|
||||
# return ClientObjectRef(pb.reference_id)
|
||||
# elif pb.local == webpb.Locality.INTERNED:
|
||||
# return cloudpickle.loads(pb.data)
|
||||
|
||||
# raise Exception("convert_from_arg: Uncovered locality enum")
|
||||
|
||||
|
||||
def convert_to_arg(val):
|
||||
out = webpb.Arg()
|
||||
if isinstance(val, ClientObjectRef):
|
||||
out.local = webpb.Locality.REFERENCE
|
||||
out.reference_id = val.id
|
||||
else:
|
||||
out.local = webpb.Locality.INTERNED
|
||||
out.data = cloudpickle.dumps(val)
|
||||
return out
|
||||
40
web_python/ray/web.py
Normal file
40
web_python/ray/web.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from js import XMLHttpRequest, Blob
|
||||
|
||||
import json
|
||||
import base64
|
||||
import cloudpickle
|
||||
|
||||
|
||||
def get(get_id):
|
||||
enc = base64.standard_b64encode(get_id).decode()
|
||||
body = json.dumps({"id": enc})
|
||||
req = XMLHttpRequest.new()
|
||||
req.open("POST", "/api/get", False)
|
||||
blob = Blob.new([body], {type: 'application/json'})
|
||||
req.send(blob)
|
||||
out = json.loads(req.response)
|
||||
return out
|
||||
|
||||
|
||||
def put(obj):
|
||||
data = cloudpickle.dumps(obj)
|
||||
enc = base64.standard_b64encode(data).decode()
|
||||
body = json.dumps({"data": enc})
|
||||
req = XMLHttpRequest.new()
|
||||
req.open("POST", "/api/put", False)
|
||||
blob = Blob.new([body], {type: 'application/json'})
|
||||
req.send(blob)
|
||||
out = json.loads(req.response)
|
||||
out_id = base64.standard_b64decode(out["id"])
|
||||
return out_id
|
||||
|
||||
|
||||
def schedule(task):
|
||||
body = json.dumps(task)
|
||||
req = XMLHttpRequest.new()
|
||||
req.open("POST", "/api/schedule", False)
|
||||
blob = Blob.new([body], {type: 'application/json'})
|
||||
req.send(blob)
|
||||
out = json.loads(req.response)
|
||||
out_id = base64.standard_b64decode(out["return_id"])
|
||||
return out_id
|
||||
57
web_python/ray/webpb.py
Normal file
57
web_python/ray/webpb.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from enum import Enum
|
||||
import base64
|
||||
import cloudpickle
|
||||
|
||||
|
||||
class ClientTaskType(Enum):
|
||||
FUNCTION = 0
|
||||
ACTOR = 1
|
||||
METHOD = 2
|
||||
STATIC_METHOD = 3
|
||||
|
||||
|
||||
class ClientTask:
|
||||
def __init__(self):
|
||||
self.type = 0
|
||||
self.name = ""
|
||||
self.payload_id = b''
|
||||
self.args = []
|
||||
|
||||
def toJsonable(self):
|
||||
return {
|
||||
"type": self.type.value,
|
||||
"name": self.name,
|
||||
"payload_id": base64.standard_b64encode(self.payload_id).decode(),
|
||||
"args": [x.toJsonable() for x in self.args]
|
||||
}
|
||||
|
||||
|
||||
class Locality(Enum):
|
||||
INTERNED = 0
|
||||
REFERENCE = 1
|
||||
|
||||
|
||||
class Arg:
|
||||
def __init__(self):
|
||||
self.local = Locality.INTERNED
|
||||
self.reference_id = b''
|
||||
self.data = b''
|
||||
self.type = 0
|
||||
|
||||
def toJsonable(self):
|
||||
return {
|
||||
"local": self.local.value,
|
||||
"reference_id": base64.standard_b64encode(self.reference_id).decode(),
|
||||
"data": base64.standard_b64encode(self.data).decode(),
|
||||
"type": self.type,
|
||||
}
|
||||
|
||||
|
||||
def loads(b64):
|
||||
data = base64.standard_b64decode(b64)
|
||||
return cloudpickle.loads(data)
|
||||
|
||||
|
||||
def dumps(obj):
|
||||
data = cloudpickle.dumps(obj)
|
||||
return base64.standard_b64encode(data).decode()
|
||||
88
web_python/ray/worker.py
Normal file
88
web_python/ray/worker.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""This file includes the Worker class which sits on the client side.
|
||||
It implements the Ray API functions that are forwarded through grpc calls
|
||||
to the server.
|
||||
"""
|
||||
import inspect
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
import cloudpickle
|
||||
|
||||
import ray.webpb as webpb
|
||||
import ray.web as webcall
|
||||
from ray.common import convert_to_arg
|
||||
from ray.common import ClientObjectRef
|
||||
from ray.common import ClientActorClass
|
||||
from ray.common import ClientRemoteFunc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Worker:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get(self, ids):
|
||||
to_get = []
|
||||
single = False
|
||||
if isinstance(ids, list):
|
||||
to_get = [x.id for x in ids]
|
||||
elif isinstance(ids, ClientObjectRef):
|
||||
to_get = [ids.id]
|
||||
single = True
|
||||
else:
|
||||
raise Exception("Can't get something that's not a "
|
||||
"list of IDs or just an ID: %s" % type(ids))
|
||||
out = [self._get(x) for x in to_get]
|
||||
if single:
|
||||
out = out[0]
|
||||
return out
|
||||
|
||||
def _get(self, id: bytes):
|
||||
data = webcall.get(id)
|
||||
if not data["valid"]:
|
||||
raise Exception(
|
||||
"Client GetObject returned invalid data: id invalid?")
|
||||
return webpb.loads(data["data"])
|
||||
|
||||
def put(self, vals):
|
||||
to_put = []
|
||||
single = False
|
||||
if isinstance(vals, list):
|
||||
to_put = vals
|
||||
else:
|
||||
single = True
|
||||
to_put.append(vals)
|
||||
|
||||
out = [self._put(x) for x in to_put]
|
||||
if single:
|
||||
out = out[0]
|
||||
return out
|
||||
|
||||
def _put(self, val):
|
||||
id = webcall.put(val)
|
||||
return ClientObjectRef(id)
|
||||
|
||||
def remote(self, function_or_class, *args, **kwargs):
|
||||
# TODO(barakmich): Arguments to ray.remote
|
||||
# get captured here.
|
||||
if inspect.isfunction(function_or_class):
|
||||
return ClientRemoteFunc(function_or_class)
|
||||
elif inspect.isclass(function_or_class):
|
||||
return ClientActorClass(function_or_class)
|
||||
else:
|
||||
raise TypeError("The @ray.remote decorator must be applied to "
|
||||
"either a function or to a class.")
|
||||
|
||||
def call_remote(self, instance, kind, *args, **kwargs):
|
||||
task = instance._prepare_client_task()
|
||||
for arg in args:
|
||||
pb_arg = convert_to_arg(arg)
|
||||
task.args.append(pb_arg)
|
||||
logging.debug("Scheduling %s" % task)
|
||||
return_id = webcall.schedule(task.toJsonable())
|
||||
return ClientObjectRef(return_id)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
19
web_python/setup.py
Normal file
19
web_python/setup.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
name="ray-web", # Replace with your own username
|
||||
version="0.0.1",
|
||||
author="Ray Authors",
|
||||
author_email="ray@anyscale.com",
|
||||
description="For use in pyodide",
|
||||
long_description="A longer description",
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/ray-project/ray-web",
|
||||
packages=setuptools.find_packages(),
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue