Skip to content

Commit f545697

Browse files
authored
update per_call_policies & per_retry_policies (#18406)
* update dev doc to add per_call_policies & per_retry_policies * updates * update
1 parent c1b88c2 commit f545697

File tree

5 files changed

+162
-34
lines changed

5 files changed

+162
-34
lines changed

sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class FooServiceClient:
5252
```
5353

5454
An end user consuming this SDK may write code like so:
55+
5556
```python
5657
from azure.core.credentials import FooCredentials
5758
from azure.foo import FooServiceClient
@@ -93,23 +94,27 @@ response = client.get_foo_properties()
9394
| Parameters | Description |
9495
| --- | --- |
9596
| `pipeline` | While `PipelineClient` will create a default pipeline, users can opt to use their own pipeline by passing in a `Pipeline` object. If passed in, the other configurations will be ignored. |
97+
| `policies` | While `PipelineClient` will create a default list of `policies`, users can opt to use their own policies by passing in a `policies` object. If passed in, `config` will be ignored |
9698
| `config` | While `PipelineClient` will create a default `Configuration`, users can opt to use their own configuration by passing in a `Configuration` object. If passed in, it will be used to create a `Pipeline` object. |
99+
| `per_call_policies` | If a default `pipeline` is needed and no `policies` is passed in, `per_call_policies` will be added before the `Retry` policy |
100+
| `per_retry_policies` | If a default `pipeline` is needed and no `policies` is passed in, `per_retry_policies` will be added after the `Retry` policy. If there is no `RetryPolicy` in the pipeline, a `ValueError` will be raised |
97101
| `transport` | While `PipelineClient` will create a default `RequestsTransport`, users can opt to use their own transport by passing in a `RequestsTransport` object. If it is omitted, `PipelineClient` will honor the other described [transport customizations](#transport). |
98102

99-
100103
### Transport
101104

102105
Various combinations of sync/async HTTP libraries as well as alternative event loop implementations are available. Therefore to support the widest range of customer scenarios, we must allow a customer to easily swap out the HTTP transport layer to one of those supported.
103106

104107
The transport is the last node in the pipeline, and adheres to the same basic API as any policy within the pipeline.
105108
The only currently available transport for synchronous pipelines uses the `Requests` library:
109+
106110
```python
107111
from azure.core.pipeline.transport import RequestsTransport
108112
synchronous_transport = RequestsTransport()
109113
```
110114

111115
For asynchronous pipelines a couple of transport options are available. Each of these transports are interchangable depending on whether the user has installed various 3rd party dependencies (i.e. aiohttp or trio), and the user
112116
should easily be able to specify their chosen transport. SDK developers should use the `aiohttp` transport as the default for asynchronous pipelines where the user has not specified an alternative.
117+
113118
```python
114119
from azure.foo.aio import FooServiceClient
115120
from azure.core.pipeline.transport import (
@@ -131,6 +136,7 @@ response = await client.get_foo_properties()
131136

132137
Some common properties can be configured on all transports. They must be passed
133138
as kwargs arguments while building the transport instance. These include the following properties:
139+
134140
```python
135141
transport = AioHttpTransport(
136142
# The connect and read timeout value. Defaults to 100 seconds.
@@ -190,6 +196,7 @@ proxy_policy.proxies = {'https': 'http://user:[email protected]:1180/'}
190196
The HttpRequest and HttpResponse objects represent a generic concept of HTTP request and response constructs and are in no way tied to a particular transport or HTTP library.
191197

192198
The HttpRequest has the following API. It does not vary between transports:
199+
193200
```python
194201
class HttpRequest(object):
195202

@@ -240,6 +247,7 @@ This is to accomodate how the data is extracted for the object returned by the H
240247
There is also an async flavor: AsyncHttpResponse. This is to allow for the asynchronous streaming of
241248
data from the response.
242249
For example:
250+
243251
```python
244252
from azure.core.pipeline.transport import (
245253
RequestsTransportResponse, # HttpResponse
@@ -248,10 +256,12 @@ from azure.core.pipeline.transport import (
248256
AsyncioRequestsTransportResponse, # AsyncHttpResponse
249257
)
250258
```
259+
251260
The API for each of these response types is identical, so the consumer of the Response need not know about these
252261
particular types.
253262

254263
The HttpResponse has the following API. It does not vary between transports:
264+
255265
```python
256266
class HttpResponse(object):
257267

@@ -297,6 +307,7 @@ transport specific and can contain data persisted between pipeline requests (for
297307
pool or "session"), as well as used by the SDK developer to carry arbitrary data through the pipeline.
298308

299309
The API for PipelineRequest and PipelineResponse is as follows:
310+
300311
```python
301312
class PipelineRequest(object):
302313

@@ -327,6 +338,7 @@ This is a simple abstract class, that can act before the request is done, or aft
327338
- Logging the request and/or response
328339

329340
A SansIOHTTPPolicy should implement one or more of the following methods:
341+
330342
```python
331343
def on_request(self, request):
332344
"""Is executed before sending the request to next policy."""
@@ -345,6 +357,7 @@ def on_exception(self, request):
345357
SansIOHTTPPolicy methods can be declared as coroutines, but then they can only be used with a AsyncPipeline.
346358

347359
Current provided sans IO policies include:
360+
348361
```python
349362
from azure.core.pipeline.policies import (
350363
HeadersPolicy, # Add custom headers to all requests
@@ -364,6 +377,7 @@ Some policies are more complex, like retry strategy, and need to have control of
364377
In the current version, they are subclasses of HTTPPolicy or AsyncHTTPPolicy, and can be used only their corresponding synchronous or asynchronous pipeline type.
365378

366379
An HTTPPolicy or AsyncHTTPPolicy must implement the `send` method, and this implementation must in include a call to process the next policy in the pipeline:
380+
367381
```python
368382
class CustomPolicy(HTTPPolicy):
369383

@@ -384,6 +398,7 @@ class CustomAsyncPolicy(AsyncHTTPPolicy):
384398
```
385399

386400
Currently provided HTTP policies include:
401+
387402
```python
388403
from azure.core.pipeline.policies import (
389404
RetryPolicy,
@@ -449,7 +464,6 @@ from azure.core.pipeline.policies import (
449464
| | | retry_mode | x | x | Fixed or exponential delay between attemps, default is exponential. |
450465
| | | timeout | x | x | Timeout setting for the operation in seconds, default is `604800s` (7 days). |
451466

452-
453467
### The Pipeline
454468

455469
The pipeline itself represents a chain of policies where the final node in the chain is the HTTP transport.

sdk/core/azure-core/azure/core/_pipeline_client.py

+37-13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
DistributedTracingPolicy,
3838
HttpLoggingPolicy,
3939
RequestIdPolicy,
40+
RetryPolicy,
4041
)
4142
from .pipeline.transport import RequestsTransport
4243

@@ -111,10 +112,10 @@ def close(self):
111112
def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
112113
transport = kwargs.get('transport')
113114
policies = kwargs.get('policies')
115+
per_call_policies = kwargs.get('per_call_policies', [])
116+
per_retry_policies = kwargs.get('per_retry_policies', [])
114117

115118
if policies is None: # [] is a valid policy list
116-
per_call_policies = kwargs.get('per_call_policies', [])
117-
per_retry_policies = kwargs.get('per_retry_policies', [])
118119
policies = [
119120
RequestIdPolicy(**kwargs),
120121
config.headers_policy,
@@ -123,24 +124,47 @@ def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
123124
ContentDecodePolicy(**kwargs)
124125
]
125126
if isinstance(per_call_policies, Iterable):
126-
for policy in per_call_policies:
127-
policies.append(policy)
127+
policies.extend(per_call_policies)
128128
else:
129129
policies.append(per_call_policies)
130130

131-
policies = policies + [config.redirect_policy,
132-
config.retry_policy,
133-
config.authentication_policy,
134-
config.custom_hook_policy]
131+
policies.extend([config.redirect_policy,
132+
config.retry_policy,
133+
config.authentication_policy,
134+
config.custom_hook_policy])
135135
if isinstance(per_retry_policies, Iterable):
136-
for policy in per_retry_policies:
137-
policies.append(policy)
136+
policies.extend(per_retry_policies)
138137
else:
139138
policies.append(per_retry_policies)
140139

141-
policies = policies + [config.logging_policy,
142-
DistributedTracingPolicy(**kwargs),
143-
config.http_logging_policy or HttpLoggingPolicy(**kwargs)]
140+
policies.extend([config.logging_policy,
141+
DistributedTracingPolicy(**kwargs),
142+
config.http_logging_policy or HttpLoggingPolicy(**kwargs)])
143+
else:
144+
if isinstance(per_call_policies, Iterable):
145+
per_call_policies_list = list(per_call_policies)
146+
else:
147+
per_call_policies_list = [per_call_policies]
148+
per_call_policies_list.extend(policies)
149+
policies = per_call_policies_list
150+
151+
if isinstance(per_retry_policies, Iterable):
152+
per_retry_policies_list = list(per_retry_policies)
153+
else:
154+
per_retry_policies_list = [per_retry_policies]
155+
if len(per_retry_policies_list) > 0:
156+
index_of_retry = -1
157+
for index, policy in enumerate(policies):
158+
if isinstance(policy, RetryPolicy):
159+
index_of_retry = index
160+
if index_of_retry == -1:
161+
raise ValueError("Failed to add per_retry_policies; "
162+
"no RetryPolicy found in the supplied list of policies. ")
163+
policies_1 = policies[:index_of_retry+1]
164+
policies_2 = policies[index_of_retry+1:]
165+
policies_1.extend(per_retry_policies_list)
166+
policies_1.extend(policies_2)
167+
policies = policies_1
144168

145169
if not transport:
146170
transport = RequestsTransport(**kwargs)

sdk/core/azure-core/azure/core/_pipeline_client_async.py

+37-18
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
DistributedTracingPolicy,
3535
HttpLoggingPolicy,
3636
RequestIdPolicy,
37+
AsyncRetryPolicy,
3738
)
3839

3940
try:
@@ -73,7 +74,7 @@ class AsyncPipelineClient(PipelineClientBase):
7374
:keyword per_retry_policies: If specified, the policies will be added into the policy list after RetryPolicy
7475
:paramtype per_retry_policies: Union[AsyncHTTPPolicy, SansIOHTTPPolicy,
7576
list[AsyncHTTPPolicy], list[SansIOHTTPPolicy]]
76-
:keyword AsyncHttpTransport transport: If omitted, AioHttpTransport is used for synchronous transport.
77+
:keyword AsyncHttpTransport transport: If omitted, AioHttpTransport is used for asynchronous transport.
7778
:return: An async pipeline object.
7879
:rtype: ~azure.core.pipeline.AsyncPipeline
7980
@@ -109,10 +110,10 @@ async def close(self):
109110
def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
110111
transport = kwargs.get('transport')
111112
policies = kwargs.get('policies')
113+
per_call_policies = kwargs.get('per_call_policies', [])
114+
per_retry_policies = kwargs.get('per_retry_policies', [])
112115

113116
if policies is None: # [] is a valid policy list
114-
per_call_policies = kwargs.get('per_call_policies', [])
115-
per_retry_policies = kwargs.get('per_retry_policies', [])
116117
policies = [
117118
RequestIdPolicy(**kwargs),
118119
config.headers_policy,
@@ -121,28 +122,46 @@ def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
121122
ContentDecodePolicy(**kwargs)
122123
]
123124
if isinstance(per_call_policies, Iterable):
124-
for policy in per_call_policies:
125-
policies.append(policy)
125+
policies.extend(per_call_policies)
126126
else:
127127
policies.append(per_call_policies)
128128

129-
policies = policies + [
130-
config.redirect_policy,
131-
config.retry_policy,
132-
config.authentication_policy,
133-
config.custom_hook_policy
134-
]
129+
policies.extend([config.redirect_policy,
130+
config.retry_policy,
131+
config.authentication_policy,
132+
config.custom_hook_policy])
135133
if isinstance(per_retry_policies, Iterable):
136-
for policy in per_retry_policies:
137-
policies.append(policy)
134+
policies.extend(per_retry_policies)
138135
else:
139136
policies.append(per_retry_policies)
140137

141-
policies = policies + [
142-
config.logging_policy,
143-
DistributedTracingPolicy(**kwargs),
144-
config.http_logging_policy or HttpLoggingPolicy(**kwargs)
145-
]
138+
policies.extend([config.logging_policy,
139+
DistributedTracingPolicy(**kwargs),
140+
config.http_logging_policy or HttpLoggingPolicy(**kwargs)])
141+
else:
142+
if isinstance(per_call_policies, Iterable):
143+
per_call_policies_list = list(per_call_policies)
144+
else:
145+
per_call_policies_list = [per_call_policies]
146+
per_call_policies_list.extend(policies)
147+
policies = per_call_policies_list
148+
if isinstance(per_retry_policies, Iterable):
149+
per_retry_policies_list = list(per_retry_policies)
150+
else:
151+
per_retry_policies_list = [per_retry_policies]
152+
if len(per_retry_policies_list) > 0:
153+
index_of_retry = -1
154+
for index, policy in enumerate(policies):
155+
if isinstance(policy, AsyncRetryPolicy):
156+
index_of_retry = index
157+
if index_of_retry == -1:
158+
raise ValueError("Failed to add per_retry_policies; "
159+
"no RetryPolicy found in the supplied list of policies. ")
160+
policies_1 = policies[:index_of_retry + 1]
161+
policies_2 = policies[index_of_retry + 1:]
162+
policies_1.extend(per_retry_policies_list)
163+
policies_1.extend(policies_2)
164+
policies = policies_1
146165

147166
if not transport:
148167
from .pipeline.transport import AioHttpTransport

sdk/core/azure-core/tests/async_tests/test_pipeline_async.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
from azure.core.pipeline.policies import (
3030
SansIOHTTPPolicy,
3131
UserAgentPolicy,
32+
DistributedTracingPolicy,
3233
AsyncRetryPolicy,
3334
AsyncRedirectPolicy,
3435
AsyncHTTPPolicy,
3536
AsyncRetryPolicy,
36-
HttpLoggingPolicy
37+
HttpLoggingPolicy,
3738
)
3839
from azure.core.pipeline.transport import (
3940
AsyncHttpTransport,
@@ -286,3 +287,38 @@ def send(*args):
286287
pos_retry = policies.index(retry_policy)
287288
assert pos_boo < pos_retry
288289
assert pos_foo > pos_retry
290+
291+
policies = [UserAgentPolicy(),
292+
AsyncRetryPolicy(),
293+
DistributedTracingPolicy()]
294+
client = AsyncPipelineClient(base_url="test", policies=policies, per_call_policies=boo_policy)
295+
actual_policies = client._pipeline._impl_policies
296+
assert boo_policy == actual_policies[0]
297+
client = AsyncPipelineClient(base_url="test", policies=policies, per_call_policies=[boo_policy])
298+
actual_policies = client._pipeline._impl_policies
299+
assert boo_policy == actual_policies[0]
300+
301+
client = AsyncPipelineClient(base_url="test", policies=policies, per_retry_policies=foo_policy)
302+
actual_policies = client._pipeline._impl_policies
303+
assert foo_policy == actual_policies[2]
304+
client = AsyncPipelineClient(base_url="test", policies=policies, per_retry_policies=[foo_policy])
305+
actual_policies = client._pipeline._impl_policies
306+
assert foo_policy == actual_policies[2]
307+
308+
client = AsyncPipelineClient(base_url="test", policies=policies, per_call_policies=boo_policy,
309+
per_retry_policies=[foo_policy])
310+
actual_policies = client._pipeline._impl_policies
311+
assert boo_policy == actual_policies[0]
312+
assert foo_policy == actual_policies[3]
313+
client = AsyncPipelineClient(base_url="test", policies=policies, per_call_policies=[boo_policy],
314+
per_retry_policies=[foo_policy])
315+
actual_policies = client._pipeline._impl_policies
316+
assert boo_policy == actual_policies[0]
317+
assert foo_policy == actual_policies[3]
318+
319+
policies = [UserAgentPolicy(),
320+
DistributedTracingPolicy()]
321+
with pytest.raises(ValueError):
322+
client = AsyncPipelineClient(base_url="test", policies=policies, per_retry_policies=foo_policy)
323+
with pytest.raises(ValueError):
324+
client = AsyncPipelineClient(base_url="test", policies=policies, per_retry_policies=[foo_policy])

sdk/core/azure-core/tests/test_pipeline.py

+35
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from azure.core.pipeline.policies import (
5151
SansIOHTTPPolicy,
5252
UserAgentPolicy,
53+
DistributedTracingPolicy,
5354
RedirectPolicy,
5455
RetryPolicy,
5556
HttpLoggingPolicy,
@@ -398,6 +399,40 @@ def send(*args):
398399
assert pos_boo < pos_retry
399400
assert pos_foo > pos_retry
400401

402+
policies = [UserAgentPolicy(),
403+
RetryPolicy(),
404+
DistributedTracingPolicy()]
405+
client = PipelineClient(base_url="test", policies=policies, per_call_policies=boo_policy)
406+
actual_policies = client._pipeline._impl_policies
407+
assert boo_policy == actual_policies[0]
408+
client = PipelineClient(base_url="test", policies=policies, per_call_policies=[boo_policy])
409+
actual_policies = client._pipeline._impl_policies
410+
assert boo_policy == actual_policies[0]
411+
412+
client = PipelineClient(base_url="test", policies=policies, per_retry_policies=foo_policy)
413+
actual_policies = client._pipeline._impl_policies
414+
assert foo_policy == actual_policies[2]
415+
client = PipelineClient(base_url="test", policies=policies, per_retry_policies=[foo_policy])
416+
actual_policies = client._pipeline._impl_policies
417+
assert foo_policy == actual_policies[2]
418+
419+
client = PipelineClient(base_url="test", policies=policies, per_call_policies=boo_policy,
420+
per_retry_policies=foo_policy)
421+
actual_policies = client._pipeline._impl_policies
422+
assert boo_policy == actual_policies[0]
423+
assert foo_policy == actual_policies[3]
424+
client = PipelineClient(base_url="test", policies=policies, per_call_policies=[boo_policy],
425+
per_retry_policies=[foo_policy])
426+
actual_policies = client._pipeline._impl_policies
427+
assert boo_policy == actual_policies[0]
428+
assert foo_policy == actual_policies[3]
429+
430+
policies = [UserAgentPolicy(),
431+
DistributedTracingPolicy()]
432+
with pytest.raises(ValueError):
433+
client = PipelineClient(base_url="test", policies=policies, per_retry_policies=foo_policy)
434+
with pytest.raises(ValueError):
435+
client = PipelineClient(base_url="test", policies=policies, per_retry_policies=[foo_policy])
401436

402437
if __name__ == "__main__":
403438
unittest.main()

0 commit comments

Comments
 (0)