Skip to content

Commit de2a7cc

Browse files
authored
Merge pull request #260 from digital-asset/python-type-libs
python: Add some type libraries
2 parents bf6e906 + 2b4b2d8 commit de2a7cc

File tree

7 files changed

+118
-14
lines changed

7 files changed

+118
-14
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.5.4
1+
7.5.5

poetry.lock

Lines changed: 50 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
[tool.poetry]
55
name = "dazl"
6-
version = "7.5.4"
6+
version = "7.5.5"
77
description = "high-level Ledger API client for DAML ledgers"
88
license = "Apache-2.0"
99
authors = ["Davin K. Tanabe <davin.tanabe@digitalasset.com>"]
@@ -18,6 +18,7 @@ python = "^3.6"
1818
aiohttp = { version = "*", optional = true }
1919
dataclasses = { version = "*", python = "~=3.6.0" }
2020
google-auth = { version = "*", optional = true }
21+
googleapis_common_protos = "^1"
2122
grpcio = ">=1.32.0"
2223
oauthlib = { version = "*", optional = true }
2324
prometheus_client = { version = "*", optional = true }
@@ -42,6 +43,7 @@ sphinx = "^4"
4243
sphinx-autobuild = "*"
4344
sphinx-markdown-builder = "^0.5.4"
4445
sphinxcontrib-trio = "^1.1.2"
46+
types-protobuf = "*"
4547
watchdog = "*"
4648

4749
[tool.poetry.extras]

python/dazl/ledger/errors.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
4-
from typing import TYPE_CHECKING, Callable, TypeVar
3+
import asyncio
4+
import concurrent.futures
5+
from typing import TYPE_CHECKING, Callable, Optional, TypeVar
56

67
if TYPE_CHECKING:
78
from . import Connection
@@ -15,6 +16,7 @@
1516
"ConnectionClosedError",
1617
"_rewrite_exceptions",
1718
"_translate_exceptions",
19+
"_allow_cancel",
1820
]
1921

2022

@@ -95,9 +97,46 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
9597
raise ConnectionClosedError() from exc_val
9698

9799

100+
class AllowCancellation:
101+
def __init__(self, allow: Callable[[], bool]):
102+
self.allow = allow
103+
104+
def __enter__(self):
105+
return self
106+
107+
def __exit__(self, exc_type, exc_val, exc_tb) -> Optional[bool]:
108+
if exc_val is not None:
109+
# These two exceptions are actually the same type under the hood--however, nothing
110+
# that I can find in the Python docs suggest that this HAS to be the case. Either
111+
# way, suppress cancellation errors that happen when we are closed, because .
112+
return self.allow() and (
113+
exc_type is asyncio.CancelledError or exc_type is concurrent.futures.CancelledError
114+
)
115+
116+
return None
117+
118+
async def __aenter__(self):
119+
return self
120+
121+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> Optional[bool]:
122+
if exc_val is not None:
123+
# These two exceptions are actually the same type under the hood--however, nothing
124+
# that I can find in the Python docs suggest that this HAS to be the case. Either
125+
# way, suppress cancellation errors that happen when we are closed, because .
126+
return self.allow() and (
127+
exc_type is asyncio.CancelledError or exc_type is concurrent.futures.CancelledError
128+
)
129+
130+
return None
131+
132+
98133
def _translate_exceptions(conn: "Connection") -> ExceptionTranslator:
99134
"""
100135
Return an (async) context manager that translates exceptions thrown from low-level gRPC calls
101136
to high-level dazl exceptions.
102137
"""
103138
return ExceptionTranslator(conn)
139+
140+
141+
def _allow_cancel(allow: Callable[[], bool]) -> AllowCancellation:
142+
return AllowCancellation(allow)

python/dazl/ledger/grpc/conn_aio.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from ..api_types import ArchiveEvent, Boundary, Command, CreateEvent, ExerciseResponse, PartyInfo
6464
from ..config import Config
6565
from ..config.access import PropertyBasedAccessConfig
66-
from ..errors import ProtocolWarning, _translate_exceptions
66+
from ..errors import ProtocolWarning, _allow_cancel, _translate_exceptions
6767
from .channel import create_channel
6868
from .codec_aio import Codec
6969

@@ -644,8 +644,16 @@ def __init__(
644644
self._filters = filters
645645
self._offset_range = offset_range
646646
self._response_stream = None # type: Optional[UnaryStreamCall]
647+
self._closed = False
648+
649+
@property
650+
def is_closed(self) -> bool:
651+
return self._closed
647652

648653
async def close(self) -> None:
654+
# make sure to mark the object as "closed"; when we're closed, we don't mind cancellation
655+
# errors, because we're the one triggering the cancellation
656+
self._closed = True
649657
if self._response_stream is not None:
650658
self._response_stream.cancel()
651659
self._response_stream = None
@@ -670,7 +678,7 @@ async def items(self):
670678
In this case, the first returned object is a :class:`Boundary` with ``offset=None``.
671679
"""
672680
log = self.conn.config.logger
673-
async with _translate_exceptions(self.conn), self:
681+
async with _translate_exceptions(self.conn), self, _allow_cancel(lambda: self._closed):
674682
filters = await self.conn.codec.encode_filters(self._filters)
675683
filters_by_party = {party: filters for party in self.conn.config.access.read_as}
676684
tx_filter_pb = G_TransactionFilter(filters_by_party=filters_by_party)

python/dazl/ledgerutil/acs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
3+
import asyncio
44
from asyncio import CancelledError, Future, InvalidStateError, ensure_future, get_event_loop, sleep
55
from collections.abc import Mapping as MappingBase
66
import sys
@@ -205,6 +205,9 @@ async def _main(self) -> None:
205205
if snapshot is not None:
206206
self._snapshot = snapshot
207207
self._snapshot_fut.set_result(snapshot)
208+
except CancelledError:
209+
if self._snapshot_fut is not None and not self._snapshot_fut.done():
210+
self._snapshot_fut.cancel()
208211
except Exception as ex:
209212
# No one is actually waiting for our result, so if we don't move our exception to
210213
# an awaitable that people actually care about, it will never be seen!

python/tests/unit/test_ledgerutil_acs.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,12 @@ async def accept_roles(sandbox, party):
5353
async with conn.query("Main:InviteReceiverRole") as query:
5454
async for event in query.creates():
5555
await conn.exercise(event.contract_id, "AcceptInviteReceiverRole")
56+
57+
58+
@pytest.mark.asyncio
59+
async def test_acs_can_async_read(sandbox):
60+
async with connect_with_new_party(url=sandbox, dar=PostOffice) as p:
61+
async with ACS(p.connection, {"Main:PostmanRole"}) as acs:
62+
await acs.read()
63+
64+
assert True

0 commit comments

Comments
 (0)