Skip to content

Commit b5d49de

Browse files
authored
fix: Fix Async function support in Python
1 parent 5af84b6 commit b5d49de

File tree

8 files changed

+148
-24
lines changed

8 files changed

+148
-24
lines changed

packages/jsii-pacmak/lib/targets/python.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,11 @@ class Method extends BaseMethod {
857857
protected readonly jsiiMethod: string = "invoke";
858858
}
859859

860+
class AsyncMethod extends BaseMethod {
861+
protected readonly implicitParameter: string = "self";
862+
protected readonly jsiiMethod: string = "ainvoke";
863+
}
864+
860865
class StaticProperty extends BaseProperty {
861866
protected readonly decorator: string = "classproperty";
862867
protected readonly implicitParameter: string = "cls";
@@ -1473,15 +1478,27 @@ class PythonGenerator extends Generator {
14731478
protected onMethod(cls: spec.ClassType, method: spec.Method) {
14741479
const { parameters = [] } = method;
14751480

1476-
this.getPythonType(cls.fqn).addMember(
1477-
new Method(
1478-
toPythonMethodName(method.name!, method.protected),
1479-
method.name,
1480-
parameters,
1481-
method.returns,
1482-
{ abstract: method.abstract, liftedProp: this.getliftedProp(method) },
1483-
)
1484-
);
1481+
if (this.isAsyncMethod(method)) {
1482+
this.getPythonType(cls.fqn).addMember(
1483+
new AsyncMethod(
1484+
toPythonMethodName(method.name!, method.protected),
1485+
method.name,
1486+
parameters,
1487+
method.returns,
1488+
{ abstract: method.abstract, liftedProp: this.getliftedProp(method) },
1489+
)
1490+
);
1491+
} else {
1492+
this.getPythonType(cls.fqn).addMember(
1493+
new Method(
1494+
toPythonMethodName(method.name!, method.protected),
1495+
method.name,
1496+
parameters,
1497+
method.returns,
1498+
{ abstract: method.abstract, liftedProp: this.getliftedProp(method) },
1499+
)
1500+
);
1501+
}
14851502
}
14861503

14871504
protected onProperty(cls: spec.ClassType, prop: spec.Property) {
@@ -1651,4 +1668,8 @@ class PythonGenerator extends Generator {
16511668

16521669
return abstractBases;
16531670
}
1671+
1672+
private isAsyncMethod(method: spec.Method): boolean {
1673+
return method.returns !== undefined && method.returns.promise !== undefined && method.returns.promise;
1674+
}
16541675
}

packages/jsii-python-runtime/src/jsii/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
sget = kernel.sget
3131
sset = kernel.sset
3232
invoke = kernel.invoke
33+
ainvoke = kernel.ainvoke
3334
sinvoke = kernel.sinvoke
3435
stats = kernel.stats
3536

@@ -56,6 +57,7 @@
5657
"sget",
5758
"sset",
5859
"invoke",
60+
"ainvoke",
5961
"sinvoke",
6062
"stats",
6163
]

packages/jsii-python-runtime/src/jsii/_kernel/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
from jsii._kernel.types import (
1616
EnumRef,
1717
LoadRequest,
18+
BeginRequest,
19+
CallbacksRequest,
1820
CreateRequest,
21+
CompleteRequest,
1922
DeleteRequest,
23+
EndRequest,
2024
GetRequest,
2125
InvokeRequest,
2226
SetRequest,
@@ -36,6 +40,12 @@ class Object:
3640
__jsii_type__ = "Object"
3741

3842

43+
def _handle_callback(kernel, callback):
44+
obj = _reference_map.resolve_id(callback.invoke.objref.ref)
45+
method = getattr(obj, callback.cookie)
46+
return method(*callback.invoke.args)
47+
48+
3949
def _get_overides(klass: JSClass, obj: Any) -> List[Override]:
4050
overrides = []
4151

@@ -218,6 +228,42 @@ def sinvoke(
218228
)
219229
).result
220230

231+
@_dereferenced
232+
def ainvoke(
233+
self, obj: Referenceable, method: str, args: Optional[List[Any]] = None
234+
) -> Any:
235+
if args is None:
236+
args = []
237+
238+
promise = self.provider.begin(
239+
BeginRequest(
240+
objref=obj.__jsii_ref__,
241+
method=method,
242+
args=_make_reference_for_native(self, args),
243+
)
244+
)
245+
246+
callbacks = self.provider.callbacks(CallbacksRequest()).callbacks
247+
while callbacks:
248+
for callback in callbacks:
249+
try:
250+
result = _handle_callback(self, callback)
251+
except Exception as exc:
252+
# TODO: Maybe we want to print the whole traceback here?
253+
complete = self.provider.complete(
254+
CompleteRequest(cbid=callback.cbid, err=str(exc))
255+
)
256+
else:
257+
complete = self.provider.complete(
258+
CompleteRequest(cbid=callback.cbid, result=result)
259+
)
260+
261+
assert complete.cbid == callback.cbid
262+
263+
callbacks = self.provider.callbacks(CallbacksRequest()).callbacks
264+
265+
return self.provider.end(EndRequest(promiseid=promise.promiseid)).result
266+
221267
def stats(self):
222268
resp = self.provider.stats(StatsRequest())
223269

packages/jsii-python-runtime/src/jsii/_kernel/providers/base.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818
StaticGetRequest,
1919
StaticInvokeRequest,
2020
StaticSetRequest,
21+
BeginRequest,
22+
BeginResponse,
23+
EndRequest,
24+
EndResponse,
25+
CallbacksRequest,
26+
CallbacksResponse,
27+
CompleteRequest,
28+
CompleteResponse,
2129
StatsRequest,
2230
StatsResponse,
2331
)
@@ -66,6 +74,22 @@ def sinvoke(self, request: StaticInvokeRequest) -> InvokeResponse:
6674
def delete(self, request: DeleteRequest) -> DeleteResponse:
6775
...
6876

77+
@abc.abstractmethod
78+
def begin(self, request: BeginRequest) -> BeginResponse:
79+
...
80+
81+
@abc.abstractmethod
82+
def end(self, request: EndRequest) -> EndResponse:
83+
...
84+
85+
@abc.abstractmethod
86+
def callbacks(self, request: CallbacksRequest) -> CallbacksResponse:
87+
...
88+
89+
@abc.abstractmethod
90+
def complete(self, request: CompleteRequest) -> CompleteResponse:
91+
...
92+
6993
@abc.abstractmethod
7094
def stats(self, request: Optional[StatsRequest] = None) -> StatsResponse:
7195
...

packages/jsii-python-runtime/src/jsii/_kernel/providers/process.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@
4242
StaticGetRequest,
4343
StaticInvokeRequest,
4444
StaticSetRequest,
45+
BeginRequest,
46+
BeginResponse,
47+
EndRequest,
48+
EndResponse,
49+
CallbacksRequest,
50+
CallbacksResponse,
51+
CompleteRequest,
52+
CompleteResponse,
4553
StatsRequest,
4654
StatsResponse,
4755
)
@@ -183,6 +191,21 @@ def __init__(self):
183191
StaticInvokeRequest,
184192
_with_api_key("sinvoke", self._serializer.unstructure_attrs_asdict),
185193
)
194+
self._serializer.register_unstructure_hook(
195+
BeginRequest,
196+
_with_api_key("begin", self._serializer.unstructure_attrs_asdict),
197+
)
198+
self._serializer.register_unstructure_hook(
199+
EndRequest, _with_api_key("end", self._serializer.unstructure_attrs_asdict)
200+
)
201+
self._serializer.register_unstructure_hook(
202+
CallbacksRequest,
203+
_with_api_key("callbacks", self._serializer.unstructure_attrs_asdict),
204+
)
205+
self._serializer.register_unstructure_hook(
206+
CompleteRequest,
207+
_with_api_key("complete", self._serializer.unstructure_attrs_asdict),
208+
)
186209
self._serializer.register_unstructure_hook(
187210
StatsRequest,
188211
_with_api_key("stats", self._serializer.unstructure_attrs_asdict),
@@ -337,6 +360,18 @@ def sinvoke(self, request: StaticInvokeRequest) -> InvokeResponse:
337360
def delete(self, request: DeleteRequest) -> DeleteResponse:
338361
return self._process.send(request, DeleteResponse)
339362

363+
def begin(self, request: BeginRequest) -> BeginResponse:
364+
return self._process.send(request, BeginResponse)
365+
366+
def end(self, request: EndRequest) -> EndResponse:
367+
return self._process.send(request, EndResponse)
368+
369+
def callbacks(self, request: CallbacksRequest) -> CallbacksResponse:
370+
return self._process.send(request, CallbacksResponse)
371+
372+
def complete(self, request: CompleteRequest) -> CompleteResponse:
373+
return self._process.send(request, CompleteResponse)
374+
340375
def stats(self, request: Optional[StatsRequest] = None) -> StatsResponse:
341376
if request is None:
342377
request = StatsRequest()

packages/jsii-python-runtime/src/jsii/_kernel/types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ class EndResponse:
164164
class Callback:
165165

166166
cbid: str
167-
cookie: Optional[str]
168-
invoke: Optional[InvokeRequest]
169-
get: Optional[GetRequest]
170-
set: Optional[SetRequest]
167+
cookie: Optional[str] = None
168+
invoke: Optional[InvokeRequest] = None
169+
get: Optional[GetRequest] = None
170+
set: Optional[SetRequest] = None
171171

172172

173173
@attr.s(auto_attribs=True, frozen=True, slots=True)

packages/jsii-python-runtime/src/jsii/_reference_map.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,13 @@ def resolve(self, kernel, ref):
9898

9999
return inst
100100

101+
def resolve_id(self, id):
102+
return self._refs[id]
103+
101104

102105
_refs = _ReferenceMap(_types)
103106

104107

105108
register_reference = _refs.register
106109
resolve_reference = _refs.resolve
110+
resolve_id = _refs.resolve_id

packages/jsii-python-runtime/tests/test_compliance.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@
4949
# Tests as closely as possible to make keeping them in sync easier.
5050

5151
# These map distinct reasons for failures, so we an easily find them.
52-
xfail_async = pytest.mark.xfail(reason="Implement async methods", strict=True)
53-
xfail_callbacks = pytest.mark.xfail(reason="Implement callback support", strict=True)
52+
xfail_callbacks = pytest.mark.skip(reason="Implement callback support")
5453

5554

5655
class DerivedFromAllTypes(AllTypes):
@@ -464,39 +463,33 @@ def test_creationOfNativeObjectsFromJavaScriptObjects():
464463
assert unmarshalled_native_obj.__class__ == MulTen
465464

466465

467-
@xfail_async
468466
def test_asyncOverrides_callAsyncMethod():
469467
obj = AsyncVirtualMethods()
470468
assert obj.call_me() == 128
471469
assert obj.override_me(44) == 528
472470

473471

474-
@xfail_async
475472
def test_asyncOverrides_overrideAsyncMethod():
476473
obj = OverrideAsyncMethods()
477474
obj.call_me() == 4452
478475

479476

480-
@xfail_async
481477
def test_asyncOverrides_overrideAsyncMethodByParentClass():
482478
obj = OverrideAsyncMethodsByBaseClass()
483479
obj.call_me() == 4452
484480

485481

486-
@xfail_async
487482
def test_asyncOverrides_overrideCallsSuper():
488483
obj = OverrideCallsSuper()
489484
assert obj.override_me(12) == 1441
490485
assert obj.call_me() == 1209
491486

492487

493-
@xfail_async
494488
def test_asyncOverrides_twoOverrides():
495489
obj = TwoOverrides()
496490
assert obj.call_me() == 684
497491

498492

499-
@xfail_async
500493
def test_asyncOverrides_overrideThrows():
501494
class ThrowingAsyncVirtualMethods(AsyncVirtualMethods):
502495
def override_me(self, mult):
@@ -783,15 +776,14 @@ def test_reservedKeywordsAreSlugifiedInMethodNames():
783776
obj.return_()
784777

785778

786-
@xfail_async
787779
def test_nodeStandardLibrary():
788780
obj = NodeStandardLibrary()
789781

790782
assert obj.fs_read_file() == "Hello, resource!"
791783
assert obj.fs_read_file_sync() == "Hello, resource! SYNC!"
792-
assert len(obj.get_os_platform()) > 0
784+
assert len(obj.os_platform) > 0
793785
assert (
794-
obj.crypto_sha_256()
786+
obj.crypto_sha256()
795787
== "6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50"
796788
)
797789

0 commit comments

Comments
 (0)