automated terminal push

This commit is contained in:
lenape
2025-06-27 16:06:02 +00:00
parent 511dd3b36b
commit 6a954eb013
4221 changed files with 2916190 additions and 1 deletions

View File

@@ -0,0 +1,491 @@
import datetime
import inspect
import itertools
from types import FunctionType, MethodType, BuiltinFunctionType, LambdaType
from typing import Callable, cast, Any, List, Optional, Sequence, Type
import functools
import attr
import enum
from ..errors import JSIIError
from .. import _reference_map
from .._utils import Singleton
from .providers import BaseProvider, ProcessProvider
from .types import (
EnumRef,
LoadRequest,
BeginRequest,
BeginResponse,
Callback,
CallbacksRequest,
CompleteRequest,
CompleteRequest,
CompleteResponse,
CreateRequest,
CreateResponse,
DeleteRequest,
EndRequest,
EnumRef,
GetRequest,
GetResponse,
InvokeRequest,
InvokeResponse,
GetScriptCommandRequest,
GetScriptCommandResponse,
InvokeScriptRequest,
InvokeScriptResponse,
KernelResponse,
LoadRequest,
ObjRef,
Override,
SetRequest,
SetResponse,
StaticGetRequest,
StaticInvokeRequest,
StaticSetRequest,
StatsRequest,
)
from .._utils import Singleton
_nothing = object()
class Object:
__jsii_type__ = "Object"
def _get_overides(klass: Type, obj: Any) -> List[Override]:
overrides: List[Override] = []
# We need to inspect each item in the MRO, until we get to our Type, at that
# point we'll bail, because those methods are not the overriden methods, but the
# "real" methods.
jsii_name = getattr(klass, "__jsii_type__", "Object")
jsii_classes = [
next(
(
m
for m in type(obj).mro()
if getattr(m, "__jsii_declared_type__", None) == jsii_name
),
Object,
)
] + list(
itertools.chain.from_iterable(
(getattr(m, "__jsii_ifaces__", []) for m in type(obj).mro())
)
)
for mro_klass in type(obj).mro():
if getattr(mro_klass, "__jsii_declared_type__", None) is not None:
# There is a jsii declared type, so we reached a "well known" object,
# and nothing from now on is an override.
break
if mro_klass is Object or mro_klass is object:
break
for name, item in mro_klass.__dict__.items():
# Ignore all "special" members (name starting with __)...
if name.startswith("__"):
continue
# We're only interested in things that also exist on the JSII class or
# interfaces, and which are themselves, jsii members.
for jsii_class in jsii_classes:
original = getattr(jsii_class, name, _nothing)
if original is not _nothing:
if inspect.isfunction(item) and hasattr(original, "__jsii_name__"):
if any(
entry.method == cast(Any, original).__jsii_name__
for entry in overrides
):
# Don't re-register an override we already discovered through a previous type
continue
overrides.append(
Override(
method=cast(Any, original).__jsii_name__, cookie=name
)
)
break
elif inspect.isdatadescriptor(item) and hasattr(
getattr(original, "fget", None), "__jsii_name__"
):
if any(
entry.property == cast(Any, original).fget.__jsii_name__
for entry in overrides
):
# Don't re-register an override we already discovered through a previous type
continue
overrides.append(
Override(
property=cast(Any, original).fget.__jsii_name__,
cookie=name,
)
)
break
return overrides
def _recursize_dereference(kernel: "Kernel", d: Any) -> Any:
if isinstance(d, dict):
return {k: _recursize_dereference(kernel, v) for k, v in d.items()}
elif isinstance(d, list):
return [_recursize_dereference(kernel, i) for i in d]
elif isinstance(d, ObjRef):
return _reference_map.resolve_reference(kernel, d)
elif isinstance(d, EnumRef):
return _recursize_dereference(kernel, d.ref)(d.member)
else:
return d
def _dereferenced(fn: Callable) -> Callable:
@functools.wraps(fn)
def wrapped(kernel: "Kernel", *args: Any, **kwargs: Any):
return _recursize_dereference(kernel, fn(kernel, *args, **kwargs))
return wrapped
# We need to recurse through our data structure and look for anything that the JSII
# doesn't natively handle. These items will be created as "Object" types in the JSII.
def _make_reference_for_native(kernel: "Kernel", d: Any) -> Any:
if isinstance(d, dict):
return {
"$jsii.map": {
k: _make_reference_for_native(kernel, v) for k, v in d.items()
}
}
elif isinstance(d, list):
return [_make_reference_for_native(kernel, i) for i in d]
if getattr(d, "__jsii_type__", None) is not None:
typeFqn = getattr(d, "__jsii_type__")
if isinstance(d, enum.Enum):
return {"$jsii.enum": f"{typeFqn}/{d.value}"}
# Ugly delayed import here because I can't solve the cyclic
# package dependency right now :(.
from .._runtime import python_jsii_mapping
mapping = python_jsii_mapping(d)
if mapping is not None: # This means we are handling a data_type (aka Struct)
return {
"$jsii.struct": {
"fqn": typeFqn,
"data": {
jsii_name: _make_reference_for_native(
kernel, getattr(d, python_name)
)
for python_name, jsii_name in mapping.items()
},
}
}
return d
elif isinstance(d, (int, type(None), str, float, bool, datetime.datetime)):
return d
elif isinstance(d, (FunctionType, MethodType, BuiltinFunctionType, LambdaType)):
# Whether a given object is a function-like object.
# We won't use iscallable() since objects may implement __call__()
# but we still want to serialize them as normal.
raise JSIIError(
"Cannot pass function as argument here (did you mean to call this function?): %r"
% d
)
else:
kernel.create(d.__class__, d)
_reference_map.register_reference(d)
return d
def _handle_callback(kernel: "Kernel", callback: Callback) -> Any:
# need to handle get, set requests here as well as invoke requests
if callback.invoke:
obj = _reference_map.resolve_id(callback.invoke.objref.ref)
method = getattr(obj, callback.cookie)
hydrated_args = [
_recursize_dereference(kernel, a) for a in callback.invoke.args or []
]
# If keyword arguments are accepted, we may need to turn a struct into keywords...
kwargs = {} # No keyword arguments by default
params = inspect.signature(method).parameters
params_kwargs = [
name
for (name, param) in params.items()
if param.kind == inspect.Parameter.KEYWORD_ONLY
]
if len(params_kwargs) > 0:
params_pos_count = len(
[
param
for param in params.values()
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
or param.kind == inspect.Parameter.POSITIONAL_ONLY
]
)
if len(hydrated_args) > params_pos_count:
struct = hydrated_args.pop()
kwargs = {
name: getattr(struct, name)
for name in params_kwargs
if hasattr(struct, name)
}
return method(*hydrated_args, **kwargs)
elif callback.get:
obj = _reference_map.resolve_id(callback.get.objref.ref)
return getattr(obj, callback.cookie)
elif callback.set:
obj = _reference_map.resolve_id(callback.set.objref.ref)
hydrated_value = _recursize_dereference(kernel, callback.set.value)
return setattr(obj, callback.cookie, hydrated_value)
else:
raise JSIIError("Callback does not contain invoke|get|set")
def _callback_till_result(
kernel: "Kernel", response: Callback, response_type: Type[KernelResponse]
) -> Any:
while isinstance(response, Callback):
try:
result = _handle_callback(kernel, response)
except Exception as exc:
response = kernel.sync_complete(
response.cbid, str(exc), None, response_type
)
else:
response = kernel.sync_complete(response.cbid, None, result, response_type)
if isinstance(response, InvokeResponse):
return response.result
elif isinstance(response, GetResponse):
return response.value
else:
return response
@attr.s(auto_attribs=True, frozen=True, slots=True)
class Statistics:
object_count: int
class Kernel(metaclass=Singleton):
# This class translates between the Pythonic interface for the kernel, and the
# Kernel Provider interface that maps more directly to the JSII Kernel interface.
# It currently only supports the idea of a process kernel provider, however it
# should be possible to move to other providers in the future.
# TODO: We don't currently have any error handling, but we need to. This should
# probably live at the provider layer though, maybe with something catching
# them at this layer to translate it to something more Pythonic, depending
# on what the provider layer looks like.
def __init__(self, provider_class: Type[BaseProvider] = ProcessProvider) -> None:
self.provider = provider_class()
# TODO: Do we want to return anything from this method? Is the return value useful
# to anyone?
def load(self, name: str, version: str, tarball: str) -> None:
self.provider.load(LoadRequest(name=name, version=version, tarball=tarball))
def getBinScriptCommand(
self, pkgname: str, script: str, args: Optional[Sequence[str]] = None
) -> GetScriptCommandResponse:
if args is None:
args = []
return self.provider.getScriptCommand(
GetScriptCommandRequest(
assembly=pkgname,
script=script,
args=_make_reference_for_native(self, args),
)
)
def invokeBinScript(
self, pkgname: str, script: str, args: Optional[Sequence[str]] = None
) -> InvokeScriptResponse:
if args is None:
args = []
return self.provider.invokeBinScript(
InvokeScriptRequest(
assembly=pkgname,
script=script,
args=_make_reference_for_native(self, args),
)
)
# TODO: Is there a way to say that obj has to be an instance of klass?
def create(self, klass: Type, obj: Any, args: Optional[List[Any]] = None) -> ObjRef:
if args is None:
args = []
response = self.provider.create(
CreateRequest(
fqn=klass.__jsii_type__ or "Object",
args=_make_reference_for_native(self, args),
overrides=_get_overides(klass, obj),
interfaces=[
iface.__jsii_type__
for iface in getattr(klass, "__jsii_ifaces__", [])
],
)
)
if isinstance(response, Callback):
obj.__jsii_ref__ = _callback_till_result(self, response, CreateResponse)
else:
obj.__jsii_ref__ = response
# Register this to the reference map already (so it's available within the rest of the __init__)
_reference_map.register_reference(obj)
return obj.__jsii_ref__
def delete(self, ref: ObjRef) -> None:
self.provider.delete(DeleteRequest(objref=ref))
@_dereferenced
def get(self, obj: Any, property: str) -> Any:
response = self.provider.get(
GetRequest(objref=obj.__jsii_ref__, property=property)
)
if isinstance(response, Callback):
return _callback_till_result(self, response, GetResponse)
else:
return response.value
def set(self, obj: Any, property: str, value: Any) -> None:
response = self.provider.set(
SetRequest(
objref=obj.__jsii_ref__,
property=property,
value=_make_reference_for_native(self, value),
)
)
if isinstance(response, Callback):
_callback_till_result(self, response, SetResponse)
@_dereferenced
def sget(self, klass: Type, property: str) -> Any:
return self.provider.sget(
StaticGetRequest(fqn=klass.__jsii_type__, property=property)
).value
def sset(self, klass: Type, property: str, value: Any) -> None:
self.provider.sset(
StaticSetRequest(
fqn=klass.__jsii_type__,
property=property,
value=_make_reference_for_native(self, value),
)
)
@_dereferenced
def invoke(self, obj: Any, method: str, args: Optional[List[Any]] = None) -> Any:
if args is None:
args = []
response = self.provider.invoke(
InvokeRequest(
objref=obj.__jsii_ref__,
method=method,
args=_make_reference_for_native(self, args),
)
)
if isinstance(response, Callback):
return _callback_till_result(self, response, InvokeResponse)
else:
return response.result
@_dereferenced
def sinvoke(
self, klass: Type, method: str, args: Optional[List[Any]] = None
) -> Any:
if args is None:
args = []
response = self.provider.sinvoke(
StaticInvokeRequest(
fqn=klass.__jsii_type__,
method=method,
args=_make_reference_for_native(self, args),
)
)
if isinstance(response, Callback):
return _callback_till_result(self, response, InvokeResponse)
else:
return response.result
@_dereferenced
def complete(self, cbid: str, err: Optional[str], result: Any) -> Any:
return self.provider.complete(
CompleteRequest(
cbid=cbid, err=err, result=_make_reference_for_native(self, result)
)
)
def sync_complete(
self,
cbid: str,
err: Optional[str],
result: Any,
response_type: Type[KernelResponse],
) -> Any:
return self.provider.sync_complete(
CompleteRequest(
cbid=cbid, err=err, result=_make_reference_for_native(self, result)
),
response_type=response_type,
)
@_dereferenced
def ainvoke(self, obj: Any, method: str, args: Optional[List[Any]] = None) -> Any:
if args is None:
args = []
promise = self.provider.begin(
BeginRequest(
objref=obj.__jsii_ref__,
method=method,
args=_make_reference_for_native(self, args),
)
)
if isinstance(promise, Callback):
promise = _callback_till_result(self, promise, BeginResponse)
callbacks = self.provider.callbacks(CallbacksRequest()).callbacks
while callbacks:
for callback in callbacks:
try:
result = _handle_callback(self, callback)
except Exception as exc:
# TODO: Maybe we want to print the whole traceback here?
complete = self.provider.complete(
CompleteRequest(cbid=callback.cbid, err=str(exc))
)
else:
complete = self.provider.complete(
CompleteRequest(cbid=callback.cbid, result=result)
)
assert complete.cbid == callback.cbid
callbacks = self.provider.callbacks(CallbacksRequest()).callbacks
return self.provider.end(EndRequest(promiseid=promise.promiseid)).result
def stats(self):
resp = self.provider.stats(StatsRequest())
return Statistics(object_count=resp.objectCount)

View File

@@ -0,0 +1,5 @@
from .base import BaseProvider
from .process import ProcessProvider
__all__ = ["BaseProvider", "ProcessProvider"]

View File

@@ -0,0 +1,100 @@
import abc
from typing import Optional, Union, Type
from ..types import (
LoadRequest,
LoadResponse,
CreateRequest,
CreateResponse,
GetRequest,
GetResponse,
InvokeRequest,
InvokeResponse,
GetScriptCommandRequest,
GetScriptCommandResponse,
InvokeScriptRequest,
InvokeScriptResponse,
DeleteRequest,
DeleteResponse,
SetRequest,
SetResponse,
StaticGetRequest,
StaticInvokeRequest,
StaticSetRequest,
BeginRequest,
BeginResponse,
EndRequest,
EndResponse,
CallbacksRequest,
CallbacksResponse,
CompleteRequest,
CompleteResponse,
StatsRequest,
StatsResponse,
Callback,
CompleteRequest,
KernelResponse,
)
class BaseProvider(metaclass=abc.ABCMeta):
# The API provided by this Provider is not very pythonic, however it is done to map
# this API as closely to the JSII runtime as possible. Higher level abstractions
# that layer ontop of the Provider will provide a translation layer that make this
# much more Pythonic.
@abc.abstractmethod
def load(self, request: LoadRequest) -> LoadResponse: ...
@abc.abstractmethod
def getScriptCommand(
self, request: GetScriptCommandRequest
) -> GetScriptCommandResponse: ...
@abc.abstractmethod
def invokeBinScript(self, request: InvokeScriptRequest) -> InvokeScriptResponse: ...
@abc.abstractmethod
def create(self, request: CreateRequest) -> CreateResponse: ...
@abc.abstractmethod
def get(self, request: GetRequest) -> GetResponse: ...
@abc.abstractmethod
def set(self, request: SetRequest) -> SetResponse: ...
@abc.abstractmethod
def sget(self, request: StaticGetRequest) -> GetResponse: ...
@abc.abstractmethod
def sset(self, request: StaticSetRequest) -> SetResponse: ...
@abc.abstractmethod
def invoke(self, request: InvokeRequest) -> Union[InvokeResponse, Callback]: ...
@abc.abstractmethod
def sinvoke(self, request: StaticInvokeRequest) -> InvokeResponse: ...
@abc.abstractmethod
def complete(self, request: CompleteRequest) -> CompleteResponse: ...
@abc.abstractmethod
def sync_complete(
self, request: CompleteRequest, response_type: Type[KernelResponse]
) -> Union[InvokeResponse, GetResponse]: ...
@abc.abstractmethod
def delete(self, request: DeleteRequest) -> DeleteResponse: ...
@abc.abstractmethod
def begin(self, request: BeginRequest) -> BeginResponse: ...
@abc.abstractmethod
def end(self, request: EndRequest) -> EndResponse: ...
@abc.abstractmethod
def callbacks(self, request: CallbacksRequest) -> CallbacksResponse: ...
@abc.abstractmethod
def stats(self, request: Optional[StatsRequest] = None) -> StatsResponse: ...

View File

@@ -0,0 +1,424 @@
import atexit
import base64
import datetime
import contextlib
import enum
import json
import os
import os.path
import pathlib
import platform
import subprocess
import sys
import tempfile
import threading
from typing import TYPE_CHECKING, Type, Union, Mapping, IO, Any, AnyStr, Optional
import attr
import cattr # type: ignore
import dateutil.parser
import jsii._embedded.jsii
from ...__meta__ import __jsii_runtime_version__
from ..._compat import importlib_resources
from ..._utils import memoized_property
from .base import BaseProvider
from ..types import (
ObjRef,
EnumRef,
Override,
KernelRequest,
KernelResponse,
LoadRequest,
LoadResponse,
CreateRequest,
CreateResponse,
DeleteRequest,
DeleteResponse,
GetRequest,
GetResponse,
InvokeRequest,
InvokeResponse,
GetScriptCommandRequest,
GetScriptCommandResponse,
InvokeScriptRequest,
InvokeScriptResponse,
SetRequest,
SetResponse,
StaticGetRequest,
StaticInvokeRequest,
StaticSetRequest,
BeginRequest,
BeginResponse,
EndRequest,
EndResponse,
CallbacksRequest,
CallbacksResponse,
CompleteRequest,
CompleteResponse,
StatsRequest,
StatsResponse,
Callback,
CompleteRequest,
CompleteResponse,
)
from ...errors import ErrorType, JSIIError, JavaScriptError
@attr.s(auto_attribs=True, frozen=True, slots=True)
class _HelloResponse:
hello: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class _OkayResponse:
# We could technically mark this as KernelResponse, because we know that
# it is going to be one of those. However, we can't disambiguate the different
# types because some of them have the same keys as each other, so the only way
# to know what type the result is expected to be, is to know what method is
# being called. Thus we'll expect Any here, and structure this value separately.
ok: Any
@attr.s(auto_attribs=True, frozen=True, slots=True)
class _ErrorResponse:
error: str
stack: str
name: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class _CallbackResponse:
callback: Callback
@attr.s(auto_attribs=True, frozen=True, slots=True)
class _CompleteRequest:
complete: CompleteRequest
_ProcessResponse = Union[_OkayResponse, _ErrorResponse, _CallbackResponse]
def _with_api_key(api_name, asdict):
def unstructurer(value):
unstructured = asdict(value)
unstructured["api"] = api_name
return unstructured
return unstructurer
def _with_reference(data, type_):
if not isinstance(data, type_):
return type_(ref=data.ref)
return data
def _unstructure_ref(value):
return {"$jsii.byref": value.ref}
def _unstructure_enum(member):
return {"$jsii.enum": f"{member.__class__.__jsii_type__}/{member.value}"}
def ohook(d):
if d.keys() == {"$jsii.byref"} or d.keys() == {"$jsii.byref", "$jsii.interfaces"}:
return ObjRef(ref=d["$jsii.byref"], interfaces=d.get("$jsii.interfaces"))
if d.keys() == {"$jsii.date"}:
return dateutil.parser.isoparse(d["$jsii.date"])
if d.keys() == {"$jsii.enum"}:
ref, member = d["$jsii.enum"].rsplit("/", 1)
return EnumRef(ref=ObjRef(ref=ref + "@"), member=member)
if d.keys() == {"$jsii.map"}:
return d["$jsii.map"]
return d
def jdefault(obj):
if hasattr(obj, "__jsii_ref__"):
return _unstructure_ref(obj.__jsii_ref__)
if isinstance(obj, datetime.datetime) and obj.tzinfo is not None:
return {"$jsii.date": obj.isoformat()}
elif isinstance(obj, datetime.datetime):
raise TypeError("Naive datetimes are not supported, please add a timzone.")
raise TypeError("Don't know how to convert object to JSON: %r" % obj)
class _NodeProcess:
def __init__(self):
self._serializer = cattr.Converter()
self._serializer.register_unstructure_hook(enum.Enum, _unstructure_enum)
self._serializer.register_unstructure_hook(
LoadRequest,
_with_api_key("load", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
GetScriptCommandRequest,
_with_api_key(
"getBinScriptCommand", self._serializer.unstructure_attrs_asdict
),
)
self._serializer.register_unstructure_hook(
InvokeScriptRequest,
_with_api_key("invokeBinScript", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
CreateRequest,
_with_api_key("create", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
DeleteRequest,
_with_api_key("del", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
GetRequest, _with_api_key("get", self._serializer.unstructure_attrs_asdict)
)
self._serializer.register_unstructure_hook(
StaticGetRequest,
_with_api_key("sget", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
SetRequest, _with_api_key("set", self._serializer.unstructure_attrs_asdict)
)
self._serializer.register_unstructure_hook(
StaticSetRequest,
_with_api_key("sset", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
InvokeRequest,
_with_api_key("invoke", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
StaticInvokeRequest,
_with_api_key("sinvoke", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
BeginRequest,
_with_api_key("begin", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
EndRequest, _with_api_key("end", self._serializer.unstructure_attrs_asdict)
)
self._serializer.register_unstructure_hook(
CallbacksRequest,
_with_api_key("callbacks", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
CompleteRequest,
_with_api_key("complete", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
StatsRequest,
_with_api_key("stats", self._serializer.unstructure_attrs_asdict),
)
self._serializer.register_unstructure_hook(
Override, self._serializer.unstructure_attrs_asdict
)
self._serializer.register_unstructure_hook(ObjRef, _unstructure_ref)
self._serializer.register_structure_hook(ObjRef, _with_reference)
self._ctx_stack = contextlib.ExitStack()
def __del__(self):
self.stop()
def _jsii_runtime(self) -> str:
tmpdir = self._ctx_stack.enter_context(tempfile.TemporaryDirectory())
resources = {
resname: os.path.join(tmpdir, filename.replace("/", os.sep))
for resname, filename in jsii._embedded.jsii.EMBEDDED_FILES.items()
}
for resname, filename in resources.items():
pathlib.Path(os.path.dirname(filename)).mkdir(exist_ok=True)
with open(filename, "wb") as fp:
fp.write(
importlib_resources.files(jsii._embedded.jsii)
.joinpath(resname)
.read_bytes()
)
# Return our first path, which should be the path for jsii-runtime.js
return resources[jsii._embedded.jsii.ENTRYPOINT]
def _next_message(self) -> Mapping[Any, Any]:
assert self._process.stdout is not None
return json.loads(self._process.stdout.readline(), object_hook=ohook)
def start(self):
environ = os.environ.copy()
environ["JSII_AGENT"] = f"Python/{platform.python_version()}"
jsii_node = environ.get("JSII_NODE", "node")
jsii_runtime = environ.get("JSII_RUNTIME", self._jsii_runtime())
self._process = subprocess.Popen(
[
jsii_node,
"--max-old-space-size=4069",
jsii_runtime,
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=environ,
)
self.sink_thread = threading.Thread(
name="process.stderr_sink",
target=stderr_sink,
# Trailing comma here is important (this is a 1-value tuple, not a value between parentheses)
args=(self._process.stderr,),
# Thread is a daemon so it does not hold the VM from shutting down
daemon=True,
)
self.sink_thread.start()
# Clean this process up at exit, so it terminates "gracefully"
atexit.register(self.stop)
self.handshake()
def stop(self) -> None:
# This process is closing already, un-registering the hook to not fire twice
atexit.unregister(self.stop)
assert self._process.stdin is not None
if not self._process.stdin.closed:
self._process.stdin.write(b'{"exit":0}\n')
# Close the process' STDIN, singaling we are done with it
self._process.stdin.close()
try:
self._process.wait(timeout=5)
except subprocess.TimeoutExpired:
self._process.terminate()
if self.sink_thread.is_alive():
self.sink_thread.join(timeout=5)
self._ctx_stack.close()
def handshake(self) -> None:
# Get the version of the runtime that we're using.
resp: _HelloResponse = self._serializer.structure(
self._next_message(), _HelloResponse
)
# TODO: Replace with proper error.
assert (
resp.hello == f"@jsii/runtime@{__jsii_runtime_version__}"
# Transparently allow development versions of the runtime to be used.
or resp.hello == f"@jsii/runtime@0.0.0"
), f"Invalid JSII Runtime Version: {resp.hello!r}"
def send(
self, request: KernelRequest, response_type: Type[KernelResponse]
) -> KernelResponse:
req_dict = self._serializer.unstructure(request)
data = json.dumps(req_dict, default=jdefault).encode("utf8")
# Send our data, ensure that it is framed with a trailing \n
assert self._process.stdin is not None
self._process.stdin.write(b"%b\n" % (data,))
self._process.stdin.flush()
resp: _ProcessResponse = self._serializer.structure(
self._next_message(), _ProcessResponse
)
if isinstance(resp, _OkayResponse):
return self._serializer.structure(resp.ok, response_type)
elif isinstance(resp, _CallbackResponse):
return resp.callback
else:
if resp.name == ErrorType.JSII_FAULT.value:
raise JSIIError(resp.error) from JavaScriptError(resp.stack)
raise RuntimeError(resp.error) from JavaScriptError(resp.stack)
class ProcessProvider(BaseProvider):
@memoized_property
def _process(self) -> _NodeProcess:
process = _NodeProcess()
process.start()
return process
def load(self, request: LoadRequest) -> LoadResponse:
return self._process.send(request, LoadResponse)
def getScriptCommand(
self, request: GetScriptCommandRequest
) -> GetScriptCommandResponse:
return self._process.send(request, GetScriptCommandResponse)
def invokeBinScript(self, request: InvokeScriptRequest) -> InvokeScriptResponse:
return self._process.send(request, InvokeScriptResponse)
def create(self, request: CreateRequest) -> CreateResponse:
return self._process.send(request, CreateResponse)
def get(self, request: GetRequest) -> GetResponse:
return self._process.send(request, GetResponse)
def set(self, request: SetRequest) -> SetResponse:
return self._process.send(request, SetResponse)
def sget(self, request: StaticGetRequest) -> GetResponse:
return self._process.send(request, GetResponse)
def sset(self, request: StaticSetRequest) -> SetResponse:
return self._process.send(request, SetResponse)
def invoke(self, request: InvokeRequest) -> Union[InvokeResponse, Callback]:
return self._process.send(request, InvokeResponse)
def sinvoke(self, request: StaticInvokeRequest) -> InvokeResponse:
return self._process.send(request, InvokeResponse)
def delete(self, request: DeleteRequest) -> DeleteResponse:
return self._process.send(request, DeleteResponse)
def begin(self, request: BeginRequest) -> BeginResponse:
return self._process.send(request, BeginResponse)
def end(self, request: EndRequest) -> EndResponse:
return self._process.send(request, EndResponse)
def callbacks(self, request: CallbacksRequest) -> CallbacksResponse:
return self._process.send(request, CallbacksResponse)
def complete(self, request: CompleteRequest) -> CompleteResponse:
return self._process.send(request, CompleteResponse)
def sync_complete(
self, request: CompleteRequest, response_type: Type[KernelResponse]
) -> Union[InvokeResponse, GetResponse]:
resp = self._process.send(_CompleteRequest(complete=request), response_type)
return resp
def stats(self, request: Optional[StatsRequest] = None) -> StatsResponse:
if request is None:
request = StatsRequest()
return self._process.send(request, StatsResponse)
def stderr_sink(reader: IO[AnyStr]) -> None:
# An empty string is used to signal EOF...
for line in iter(reader.readline, b""):
if line == b"":
break
try:
console = json.loads(line)
if console.get("stderr") is not None:
sys.stderr.buffer.write(base64.b64decode(console["stderr"]))
if console.get("stdout") is not None:
sys.stdout.buffer.write(base64.b64decode(console["stdout"]))
except:
print(line, file=sys.stderr)

View File

@@ -0,0 +1,238 @@
from typing import Any, Dict, Generic, List, Optional, Mapping, TypeVar, Union
from typing_extensions import Protocol
import attr
@attr.s(auto_attribs=True, frozen=True, slots=True)
class ObjRef:
ref: str
interfaces: Optional[List[str]] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class EnumRef:
ref: ObjRef
member: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class Override:
method: Optional[str] = None
property: Optional[str] = None
cookie: Optional[str] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class LoadRequest:
name: str
version: str
tarball: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class LoadResponse:
assembly: str
types: int
@attr.s(auto_attribs=True, frozen=True, slots=True)
class GetScriptCommandRequest:
assembly: str
script: str
args: List[Any] = attr.Factory(list)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class GetScriptCommandResponse:
command: str
args: List[str] = attr.Factory(list)
env: Dict[str, str] = attr.Factory(dict)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class InvokeScriptRequest:
assembly: str
script: str
args: List[Any] = attr.Factory(list)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class InvokeScriptResponse:
status: int
stdout: str
stderr: str
signal: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CreateRequest:
fqn: str
args: List[Any] = attr.Factory(list)
overrides: List[Override] = attr.Factory(list)
interfaces: Optional[List[str]] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CreateResponse(ObjRef): ...
@attr.s(auto_attribs=True, frozen=True, slots=True)
class DeleteRequest:
objref: ObjRef
@attr.s(auto_attribs=True, frozen=True, slots=True)
class DeleteResponse: ...
@attr.s(auto_attribs=True, frozen=True, slots=True)
class GetRequest:
objref: ObjRef
property: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class StaticGetRequest:
fqn: str
property: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class GetResponse:
value: Any = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class StaticSetRequest:
fqn: str
property: str
value: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class SetRequest:
objref: ObjRef
property: str
value: Any
@attr.s(auto_attribs=True, frozen=True, slots=True)
class SetResponse: ...
@attr.s(auto_attribs=True, frozen=True, slots=True)
class StaticInvokeRequest:
fqn: str
method: str
args: Optional[List[Any]] = attr.Factory(list)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class InvokeRequest:
objref: ObjRef
method: str
args: Optional[List[Any]] = attr.Factory(list)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class InvokeResponse:
result: Any = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class BeginRequest:
objref: ObjRef
method: str
args: Optional[List[Any]] = attr.Factory(list)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class BeginResponse:
promiseid: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class EndRequest:
promiseid: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class EndResponse:
result: Optional[Any] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class Callback:
cbid: str
cookie: str
invoke: Optional[InvokeRequest] = None
get: Optional[GetRequest] = None
set: Optional[SetRequest] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CallbacksRequest: ...
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CallbacksResponse:
callbacks: List[Callback]
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CompleteRequest:
cbid: str
err: Optional[str] = None
result: Optional[Any] = None
@attr.s(auto_attribs=True, frozen=True, slots=True)
class CompleteResponse:
cbid: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class NamingRequest:
assembly: str
@attr.s(auto_attribs=True, frozen=True, slots=True)
class NamingResponse:
naming: Mapping[str, Mapping[str, Optional[Any]]]
@attr.s(auto_attribs=True, frozen=True, slots=True)
class StatsRequest: ...
@attr.s(auto_attribs=True, frozen=True, slots=True)
class StatsResponse:
objectCount: int
KernelRequest = Union[
LoadRequest,
CreateRequest,
DeleteRequest,
GetRequest,
SetRequest,
StaticGetRequest,
InvokeRequest,
InvokeScriptRequest,
StaticInvokeRequest,
StatsRequest,
]
KernelResponse = Union[
BeginResponse,
LoadResponse,
CreateResponse,
DeleteResponse,
GetResponse,
InvokeResponse,
InvokeScriptResponse,
SetResponse,
StatsResponse,
Callback,
]