Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
c6f446d
[EventHub] basic receive event scenario with pyamqp (#19748)
swathipil Jul 12, 2021
4134fc0
[EventHubs & AMQP Python] Send Port (#19745)
yunhaoling Jul 14, 2021
2aee77a
[EventHubs&AMQP Python] Port amqp send large message (#19937)
yunhaoling Oct 8, 2021
f11cc0f
[EventHubs] Pure Python AMQP Sycn Implementation Integration (#22397)
yunhaoling Jan 10, 2022
67df9d8
merge main
yunhaoling Jan 11, 2022
5c90e0f
[EH Pyproto] Release preparation (#22433)
yunhaoling Jan 12, 2022
dc7e79c
catch up with main and update release date
yunhaoling Jan 13, 2022
996e419
update readme to drop uamqp
yunhaoling Jan 13, 2022
986d2a4
revert async tests
yunhaoling Feb 2, 2022
e37fc23
revert aio module in eventhub
yunhaoling Feb 2, 2022
0fb1af9
merge main branch
yunhaoling Feb 8, 2022
934cb6e
[EH Pyproto] Async support (#22957)
yunhaoling Feb 9, 2022
e8bbe38
Increment version for eventhub releases (#22994)
azure-sdk Feb 9, 2022
fe47341
merge central
yunhaoling Mar 3, 2022
2805d00
[EH Pyproto] Async recv perf improvement (#23122)
yunhaoling Mar 4, 2022
17ea44b
[EH Pyproto] Release updates (#23349)
yunhaoling Mar 4, 2022
f5f9957
Increment version for eventhub releases (#23420)
azure-sdk Mar 9, 2022
0123f4d
AMQP websocket implementation (#23722)
rakshith91 May 7, 2022
bcab241
Revert "AMQP websocket implementation (#23722)" (#24344)
rakshith91 May 7, 2022
32566bd
AMQP websocket implementation (#24345)
rakshith91 May 7, 2022
3fcef28
[EventHub] basic receive event scenario with pyamqp (#19748)
swathipil Jul 12, 2021
56d551a
[EventHubs & AMQP Python] Send Port (#19745)
yunhaoling Jul 14, 2021
92abbc0
[EventHubs&AMQP Python] Port amqp send large message (#19937)
yunhaoling Oct 8, 2021
dd96d59
[EventHubs] Pure Python AMQP Sycn Implementation Integration (#22397)
yunhaoling Jan 10, 2022
e87e909
[EH Pyproto] Release preparation (#22433)
yunhaoling Jan 12, 2022
5ce4bd4
update readme to drop uamqp
yunhaoling Jan 13, 2022
cfa3f05
revert async tests
yunhaoling Feb 2, 2022
1ef957d
revert aio module in eventhub
yunhaoling Feb 2, 2022
b14a063
[EH Pyproto] Async support (#22957)
yunhaoling Feb 9, 2022
13bc27b
Increment version for eventhub releases (#22994)
azure-sdk Feb 9, 2022
82ca837
[EH Pyproto] Async recv perf improvement (#23122)
yunhaoling Mar 4, 2022
3d7227b
[EH Pyproto] Release updates (#23349)
yunhaoling Mar 4, 2022
ec8956e
Increment version for eventhub releases (#23420)
azure-sdk Mar 9, 2022
730d445
AMQP websocket implementation (#23722)
rakshith91 May 7, 2022
7305718
Revert "AMQP websocket implementation (#23722)" (#24344)
rakshith91 May 7, 2022
e3689b4
AMQP websocket implementation (#24345)
rakshith91 May 7, 2022
2f2c8e5
remove extra SR related code
swathipil May 10, 2022
c260df3
update docs + type hints
swathipil May 10, 2022
d962076
fixing failing tests
swathipil May 10, 2022
97dbf5e
[EventHubs] merge working websocket changes to feature branch (#24444)
swathipil May 16, 2022
2c188da
enable live test for ws receive
kashifkhan May 16, 2022
7ab3679
fix to create Batch properly
kashifkhan May 16, 2022
e15384e
[eventhub] Websocket timeout error exception thrown (#24504)
l0lawrence May 20, 2022
3dee1c0
[eventhub] websocket default timeout fix (#24565)
l0lawrence May 23, 2022
3eed81e
pull upstream
swathipil May 25, 2022
c9d2999
[eventhub] Custom Endpoint (#24505)
l0lawrence May 27, 2022
e60fff2
[EventHubs] pyproto - update release date + docs (#24723)
swathipil Jun 6, 2022
f62bae8
add async doc rst file
swathipil Jun 7, 2022
1ad3a2c
Increment version for eventhub releases (#24753)
azure-sdk Jun 7, 2022
07c6f05
changes to update status (#25024)
kashifkhan Jun 29, 2022
db6deca
updating codeowners file in pyproto feature
kashifkhan Jul 13, 2022
e22c380
[Eventhub] pyamqp prefetch fix (#24890)
l0lawrence Jul 13, 2022
dc73ebe
Use --no-cone in pipeline sparse checkout script (#25165) (#25208)
kashifkhan Jul 14, 2022
4eb5772
[AMQP Python] Eventhub Pyamqp tests (#24895)
l0lawrence Jul 14, 2022
f819783
[Pyamqp] Fix network logging trace in client_base (#25218)
kashifkhan Jul 15, 2022
1e0a953
reverting link credit for now (#25310)
l0lawrence Jul 20, 2022
a0739b2
[Pyamqp] Pyampq debug build Linkedin (#25296)
kashifkhan Jul 20, 2022
edfbb30
Increment version for eventhub releases (#25320)
azure-sdk Jul 20, 2022
4b20768
removing duplicate (#25321)
l0lawrence Jul 20, 2022
d663bdf
add async unit tests (#25396)
swathipil Jul 26, 2022
2eff3f8
reset logging level (#25588)
kashifkhan Aug 8, 2022
937e0de
[Pyamqp] Remember Proxy Params (#25564)
kashifkhan Aug 8, 2022
22ed214
[Pyamqp] Intial TODOS Clean Up (#25630)
kashifkhan Aug 17, 2022
6356c14
[Pyamqp] test fixes for pipeline (#25749)
l0lawrence Aug 18, 2022
378b3af
[PyAMQP] Stress testing reform (#25770)
l0lawrence Aug 19, 2022
dcacd0c
[PyAMQP] Updating pyamqp with SB changes (#25804)
l0lawrence Aug 25, 2022
78f225c
merge main
swathipil Aug 29, 2022
023973a
removing buff producer to add back in later
swathipil Aug 29, 2022
5f914e3
update cspell
swathipil Aug 29, 2022
c143274
fix uppercase link in doc/dev/issues/resolve_issues_effectively
swathipil Aug 30, 2022
9f792f0
[PyAMQP] Connections TODO (#26018)
kashifkhan Sep 6, 2022
a594bde
[PyAMQP] Kashif Client refactoring changes (#25451)
l0lawrence Sep 6, 2022
bb2e3b5
need to use pyamqp (#25895)
l0lawrence Sep 9, 2022
7810805
[EventHubs] add amqp switch support (#25965)
swathipil Sep 22, 2022
8586567
prep release
swathipil Sep 22, 2022
35c6bad
[Pyamqp] Exception Todos (#25893)
kashifkhan Sep 22, 2022
0020fc1
[Pyamqp] Async WS implementation using a pure async library (#26234)
kashifkhan Sep 29, 2022
5305372
[SB PyAMQP] Servicebus PyAMQP Working off of Anna's Branch (#24975)
l0lawrence Sep 29, 2022
ce4b257
[Pyamqp] Pyamqp fix conn (#26568)
kashifkhan Oct 1, 2022
551c4b3
Bring in changes to fix bandit from failing
kashifkhan Oct 4, 2022
5244548
[EventHubs&ServiceBus] merge sb and eh pyamqp (#26548)
swathipil Oct 5, 2022
4169d76
[Pyamqp] Fix Async Invalid Host Error Test (#26595)
kashifkhan Oct 5, 2022
327e62e
merge main
swathipil Oct 5, 2022
5517cde
fix merge conflict stress
swathipil Oct 5, 2022
5180d15
update cspell, ignore tables spelling
swathipil Oct 5, 2022
1164616
skip connection verify tests sb for now
swathipil Oct 5, 2022
27c7219
fix logging formatting (#26682)
kashifkhan Oct 7, 2022
2c21f38
Merge branch 'feature/eventhub/pyproto' of https://github.com/Azure/a…
swathipil Oct 7, 2022
35c5a7a
copy over kashifs change to sb
swathipil Oct 7, 2022
af6f191
[ServiceBus&EventHubs] pyamqp - update EH/SB docs for release (#26741)
swathipil Oct 10, 2022
7a3cfa7
[ServiceBus&EventHubs] fix mypy/pylint (#26744)
swathipil Oct 11, 2022
66942bb
prep release alpha (#26755)
swathipil Oct 11, 2022
c7b4bb4
Increment version for servicebus releases (#26762)
azure-sdk Oct 11, 2022
57ef4f1
Increment version for eventhub releases (#26766)
azure-sdk Oct 11, 2022
9b240ac
[PyAMQP] Fix logging (#26785)
l0lawrence Oct 12, 2022
4db368e
await async sleep (#26853)
kashifkhan Oct 17, 2022
6235286
merge main again
swathipil Oct 18, 2022
c3dbace
fix for async socket (#26852)
kashifkhan Oct 18, 2022
0bd401d
[Pyamqp] Possible solutions for network disruption using async websoc…
kashifkhan Oct 26, 2022
5013c2d
[Pyamqp] Fix to Improve Websocket Sync and Async Network Disruption H…
kashifkhan Oct 26, 2022
2c16b57
[pyAMQP] Stress fixes for aiohttp and valueError (#27034)
l0lawrence Nov 3, 2022
3711eeb
[Pyamqp] Changes for blocking exceptions (#27260)
kashifkhan Nov 10, 2022
0a14a3c
Stress testing updates (#27456)
l0lawrence Nov 11, 2022
4666e53
[EventHubs] kwargs/error testing (#27065)
swathipil Nov 15, 2022
a3ae1e3
[EventHubs] check for any non-None values in amqp header/properties (…
swathipil Nov 16, 2022
aa683ee
[EventHubs] update sync receive client ready flow (#27411)
swathipil Nov 17, 2022
4795f47
[pyamqp] os error add (#27351)
l0lawrence Nov 18, 2022
c82f19e
changes from perf run (#27703)
l0lawrence Nov 30, 2022
5a6176e
fix merge conflicts
swathipil Nov 30, 2022
8a3331c
Merge branch 'feature/eventhub/pyproto' into swathipil/eh/merge-featu…
swathipil Nov 30, 2022
a0cb655
Matrix Gen Stress Tests (#27754)
l0lawrence Nov 30, 2022
55150d7
revert sb to main
swathipil Nov 30, 2022
c822ace
revert non-eh files
swathipil Nov 30, 2022
745d7fd
restore samples/readmes to main
swathipil Nov 30, 2022
dd224fb
restore ci/tests/shared reqs to main
swathipil Nov 30, 2022
2e53f27
update to stable
swathipil Nov 30, 2022
9510ec3
lint + fix tests for no uamqp import
swathipil Dec 1, 2022
a343953
Merge branch 'feature/eventhub/pyproto' into swathipil/eh/merge-featu…
swathipil Dec 1, 2022
aeaab81
update test timeout
swathipil Dec 1, 2022
1031feb
re-organize changelog
swathipil Dec 1, 2022
066429a
merge main
swathipil Dec 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions sdk/eventhub/azure-eventhub/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release History

## 5.10.2 (Unreleased)
## 5.11.0 (Unreleased)

### Features Added

Expand Down Expand Up @@ -88,9 +88,66 @@ This version and all future versions will require Python 3.7+, Python 3.6 is no

## 5.9.0b1 (2022-02-09)

- The following features have been temporarily pulled out of async `EventHubProducerClient` and `EventHubConsumerClient` which will be added back in future previews as we work towards a stable release:
- Passing the following keyword arguments to the constructors and `from_connection_string` methods of the `EventHubProducerClient` and `EventHubConsumerClient` is not supported: `transport_type`, `http_proxy`, `custom_endpoint_address`, and `connection_verify`.

## 5.8.0b2 (2022-10-11)

### Features Added

- Updated the optional dependency for async transport using AMQP over WebSocket from `websocket-client` to `aiohttp` (Issue #24315, thanks @hansmbakker for the suggestion).

## 5.8.0b1 (2022-09-22)

This version and all future versions will require Python 3.7+. Python 3.6 is no longer supported.

### Other Changes

- Added the `uamqp_transport` optional parameter to the clients, to allow switching to the `uamqp` library as the transport.

## 5.8.0a5 (2022-07-19)

### Bugs Fixed

- Fixed bug that prevented token refresh at regular intervals.
- Fixed bug that was improperly passing the debug keyword argument, so that network trace debug logs are output when requested.

### Other Changes

- Added logging added in to track proper token refreshes & fetches, output exception reason for producer init failure.

## 5.8.0a4 (2022-06-07)

### Features Added

- Added support for connection using websocket and http proxy.
- Added support for custom endpoint connection over websocket.

## 5.8.0a3 (2022-03-08)

### Other Changes

- Improved the performance of async sending and receiving.

## 5.8.0a2 (2022-02-09)

### Features Added

- The classmethod `from_message_data` has been added to `EventData` for interoperability with the Schema Registry Avro Encoder library, and takes `data` and `content_type` as positional parameters.
- Added support for async `EventHubProducerClient` and `EventHubConsumerClient`.

## 5.8.0a1 (2022-01-13)

Version 5.8.0a1 is our first efforts to build an Azure Event Hubs client library based on pure python implemented AMQP stack.

### Breaking changes

- The following features have been temporarily pulled out which will be added back in future previews as we work towards a stable release:
- Async is not supported.
- Passing the following keyword arguments to the constructors and `from_connection_string` methods of the `EventHubProducerClient` and `EventHubConsumerClient` is not supported: `transport_type`, `http_proxy`, `custom_endpoint_address`, and `connection_verify`.

### Other Changes

- uAMQP dependency is removed.

## 5.7.0 (2022-01-12)

Expand Down Expand Up @@ -598,4 +655,6 @@ Version 5.0.0b1 is a preview of our efforts to create a client library that is u
- Further testing and minor bug fixes.


![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-python/sdk/eventhub/azure-eventhub/HISTORY.png)
## 0.2.0a2 (2018-04-02)

- Updated uAQMP dependency.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(
max_message_size_on_link: int,
executor: ThreadPoolExecutor,
*,
amqp_transport: AmqpTransport,
max_buffer_length: int,
max_wait_time: float = 1
):
Expand All @@ -50,10 +51,11 @@ def __init__(
self._max_message_size_on_link = max_message_size_on_link
self._check_max_wait_time_future = None
self.partition_id = partition_id
self._amqp_transport = amqp_transport

def start(self):
with self._lock:
self._cur_batch = EventDataBatch(self._max_message_size_on_link)
self._cur_batch = EventDataBatch(self._max_message_size_on_link, amqp_transport=self._amqp_transport)
self._running = True
if self._max_wait_time:
self._last_send_time = time.time()
Expand Down Expand Up @@ -113,12 +115,12 @@ def put_events(self, events, timeout_time=None):
self._buffered_queue.put(self._cur_batch)
self._buffered_queue.put(events)
# create a new batch for incoming events
self._cur_batch = EventDataBatch(self._max_message_size_on_link)
self._cur_batch = EventDataBatch(self._max_message_size_on_link, amqp_transport=self._amqp_transport)
except ValueError:
# add single event exceeds the cur batch size, create new batch
with self._lock:
self._buffered_queue.put(self._cur_batch)
self._cur_batch = EventDataBatch(self._max_message_size_on_link)
self._cur_batch = EventDataBatch(self._max_message_size_on_link, amqp_transport=self._amqp_transport)
self._cur_batch.add(events)
with self._lock:
self._cur_buffered_len += new_events_len
Expand Down Expand Up @@ -197,7 +199,7 @@ def flush(self, timeout_time=None, raise_error=True):
self._last_send_time = time.time()
#reset buffered count
self._cur_buffered_len = 0
self._cur_batch = EventDataBatch(self._max_message_size_on_link)
self._cur_batch = EventDataBatch(self._max_message_size_on_link, amqp_transport=self._amqp_transport)
_LOGGER.info("Partition %r finished flushing.", self.partition_id)

def check_max_wait_time_worker(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from ..exceptions import EventDataSendError, ConnectError, EventHubError

if TYPE_CHECKING:
from .._producer_client import SendEventTypes
from .._transport._base import AmqpTransport
from .._producer_client import SendEventTypes

_LOGGER = logging.getLogger(__name__)

Expand All @@ -31,6 +31,7 @@ def __init__(
eventhub_name: str,
max_message_size_on_link: int,
*,
amqp_transport: AmqpTransport,
max_buffer_length: int = 1500,
max_wait_time: float = 1,
executor: Optional[Union[ThreadPoolExecutor, int]] = None
Expand All @@ -47,6 +48,7 @@ def __init__(
self._max_wait_time = max_wait_time
self._max_buffer_length = max_buffer_length
self._existing_executor = False
self._amqp_transport = amqp_transport

if not executor:
self._executor = ThreadPoolExecutor()
Expand Down Expand Up @@ -88,6 +90,7 @@ def enqueue_events(
executor=self._executor,
max_wait_time=self._max_wait_time,
max_buffer_length=self._max_buffer_length,
amqp_transport = self._amqp_transport,
)
buffered_producer.start()
self._buffered_producers[pid] = buffered_producer
Expand Down
59 changes: 39 additions & 20 deletions sdk/eventhub/azure-eventhub/azure/eventhub/_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import collections
from typing import Any, Dict, Tuple, List, Optional, TYPE_CHECKING, cast, Union
try:
from typing import TypeAlias
from typing import TypeAlias # type: ignore
except ImportError:
from typing_extensions import TypeAlias
from datetime import timedelta
Expand All @@ -25,11 +25,15 @@
from azure.core.utils import parse_connection_string as core_parse_connection_string
from azure.core.pipeline.policies import RetryMode


from ._transport._uamqp_transport import UamqpTransport
try:
from ._transport._uamqp_transport import UamqpTransport
except ImportError:
UamqpTransport = None # type: ignore
from ._transport._pyamqp_transport import PyamqpTransport
from .exceptions import ClientClosedError
from ._configuration import Configuration
from ._utils import utc_from_timestamp, parse_sas_credential, generate_sas_token
from ._utils import utc_from_timestamp, parse_sas_credential
from ._pyamqp.utils import generate_sas_token
from ._connection_manager import get_connection_manager
from ._constants import (
CONTAINER_PREFIX,
Expand All @@ -43,8 +47,14 @@

if TYPE_CHECKING:
from azure.core.credentials import TokenCredential
from uamqp import Message as uamqp_Message
from uamqp.authentication import JWTTokenAuth as uamqp_JWTTokenAuth
try:
from uamqp import Message as uamqp_Message
from uamqp.authentication import JWTTokenAuth as uamqp_JWTTokenAuth
except ImportError:
uamqp_Message = None
uamqp_JWTTokenAuth = None
from ._pyamqp.message import Message
from ._pyamqp.authentication import JWTTokenAuth

_LOGGER = logging.getLogger(__name__)
_Address = collections.namedtuple("_Address", "hostname path")
Expand Down Expand Up @@ -165,7 +175,7 @@ def _get_backoff_time(retry_mode, backoff_factor, backoff_max, retried_times):
if retry_mode == RetryMode.Fixed:
backoff_value = backoff_factor
else:
backoff_value = backoff_factor * (2**retried_times)
backoff_value = backoff_factor * (2 ** retried_times)
return min(backoff_max, backoff_value)


Expand Down Expand Up @@ -262,6 +272,7 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
return AccessToken(signature, expiry)


# separate TYPE_CHECKING block here for EventHubSharedKeyCredential, o/w mypy raised error even with forward referencing
if TYPE_CHECKING:
from azure.core.credentials import TokenCredential

Expand All @@ -281,8 +292,10 @@ def __init__(
credential: CredentialTypes,
**kwargs: Any,
) -> None:
uamqp_transport = kwargs.pop("uamqp_transport", True)
self._amqp_transport = kwargs.pop("amqp_transport", UamqpTransport)
uamqp_transport = kwargs.pop("uamqp_transport", False)
if uamqp_transport and not UamqpTransport:
raise ValueError("To use the uAMQP transport, please install `uamqp>=1.6.0,<2.0.0`.")
self._amqp_transport = kwargs.pop("amqp_transport", UamqpTransport if uamqp_transport else PyamqpTransport)

self.eventhub_name = eventhub_name
if not eventhub_name:
Expand All @@ -305,7 +318,10 @@ def __init__(
**kwargs,
)
self._debug = self._config.network_tracing
self._conn_manager = get_connection_manager(**kwargs)
kwargs["custom_endpoint_address"] = self._config.custom_endpoint_address
self._conn_manager = get_connection_manager(
amqp_transport=self._amqp_transport,
**kwargs)
self._idle_timeout = kwargs.get("idle_timeout", None)

@staticmethod
Expand All @@ -322,7 +338,7 @@ def _from_connection_string(conn_str, **kwargs):
kwargs["credential"] = EventHubSharedKeyCredential(policy, key)
return kwargs

def _create_auth(self) -> uamqp_JWTTokenAuth:
def _create_auth(self) -> Union[uamqp_JWTTokenAuth, JWTTokenAuth]:
"""
Create an ~uamqp.authentication.SASTokenAuth instance
to authenticate the session.
Expand Down Expand Up @@ -381,7 +397,7 @@ def _backoff(
raise last_exception

def _management_request(
self, mgmt_msg: uamqp_Message, op_type: bytes
self, mgmt_msg: Union[uamqp_Message, Message], op_type: bytes
) -> Any:
# pylint:disable=assignment-from-none
retried_times = 0
Expand All @@ -401,26 +417,29 @@ def _management_request(
mgmt_msg.application_properties[
"security_token"
] = self._amqp_transport.get_updated_token(mgmt_auth)
response = self._amqp_transport.mgmt_client_request(
status_code, description, response = self._amqp_transport.mgmt_client_request(
mgmt_client,
mgmt_msg,
operation=READ_OPERATION,
operation_type=op_type,
status_code_field=MGMT_STATUS_CODE,
description_fields=MGMT_STATUS_DESC,
)
status_code = int(response.application_properties[MGMT_STATUS_CODE])
description = response.application_properties.get(
MGMT_STATUS_DESC
) # type: Optional[Union[str, bytes]]
status_code = int(status_code)
if description and isinstance(description, bytes):
description = description.decode("utf-8")
if status_code < 400:
return response
raise self._amqp_transport.get_error(status_code, description)
except Exception as exception: # pylint: disable=broad-except
# is_consumer=True passed in here, ALTHOUGH this method is shared by the producer and consumer.
# is_consumer will only be checked if FileNotFoundError is raised by self.mgmt_client.open() due to
# invalid/non-existent connection_verify filepath. The producer will encounter the FileNotFoundError
# when opening the SendClient, so is_consumer=True will not be passed to amqp_transport.handle_exception
# there. This is for uamqp exception parity, which raises FileNotFoundError in the consumer and
# EventHubError in the producer. TODO: Remove `is_consumer` kwarg when resolving issue #27128.
last_exception = self._amqp_transport._handle_exception( # pylint: disable=protected-access
exception, self
exception, self, is_consumer=True
)
self._backoff(
retried_times=retried_times, last_exception=last_exception
Expand Down Expand Up @@ -540,10 +559,10 @@ def _close_connection(self):
self._close_handler()
self._client._conn_manager.reset_connection_if_broken() # pylint: disable=protected-access

def _handle_exception(self, exception):
def _handle_exception(self, exception, *, is_consumer=False):
exception = self._amqp_transport.check_timeout_exception(self, exception)
return self._amqp_transport._handle_exception( # pylint: disable=protected-access
exception, self
exception, self, is_consumer=is_consumer
)

def _do_retryable_operation(self, operation, timeout=None, **kwargs):
Expand Down
Loading