corehttp 1.0.0b5__py3-none-any.whl → 1.0.0b7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. corehttp/_version.py +1 -1
  2. corehttp/credentials.py +66 -25
  3. corehttp/exceptions.py +7 -6
  4. corehttp/instrumentation/__init__.py +9 -0
  5. corehttp/instrumentation/tracing/__init__.py +14 -0
  6. corehttp/instrumentation/tracing/_decorator.py +189 -0
  7. corehttp/instrumentation/tracing/_models.py +72 -0
  8. corehttp/instrumentation/tracing/_tracer.py +69 -0
  9. corehttp/instrumentation/tracing/opentelemetry.py +277 -0
  10. corehttp/instrumentation/tracing/utils.py +31 -0
  11. corehttp/paging.py +13 -0
  12. corehttp/rest/_aiohttp.py +21 -9
  13. corehttp/rest/_http_response_impl.py +9 -15
  14. corehttp/rest/_http_response_impl_async.py +2 -0
  15. corehttp/rest/_httpx.py +9 -9
  16. corehttp/rest/_requests_basic.py +17 -10
  17. corehttp/rest/_rest_py3.py +6 -10
  18. corehttp/runtime/pipeline/__init__.py +5 -9
  19. corehttp/runtime/pipeline/_base.py +3 -2
  20. corehttp/runtime/pipeline/_base_async.py +6 -8
  21. corehttp/runtime/pipeline/_tools.py +18 -2
  22. corehttp/runtime/pipeline/_tools_async.py +2 -4
  23. corehttp/runtime/policies/__init__.py +2 -0
  24. corehttp/runtime/policies/_authentication.py +76 -24
  25. corehttp/runtime/policies/_authentication_async.py +66 -21
  26. corehttp/runtime/policies/_distributed_tracing.py +169 -0
  27. corehttp/runtime/policies/_retry.py +8 -12
  28. corehttp/runtime/policies/_retry_async.py +5 -9
  29. corehttp/runtime/policies/_universal.py +15 -11
  30. corehttp/serialization.py +237 -3
  31. corehttp/settings.py +59 -0
  32. corehttp/transport/_base.py +1 -3
  33. corehttp/transport/_base_async.py +1 -3
  34. corehttp/transport/aiohttp/_aiohttp.py +41 -16
  35. corehttp/transport/requests/_bigger_block_size_http_adapters.py +1 -1
  36. corehttp/transport/requests/_requests_basic.py +33 -18
  37. corehttp/utils/_enum_meta.py +1 -1
  38. corehttp/utils/_utils.py +2 -1
  39. corehttp-1.0.0b7.dist-info/METADATA +196 -0
  40. corehttp-1.0.0b7.dist-info/RECORD +61 -0
  41. {corehttp-1.0.0b5.dist-info → corehttp-1.0.0b7.dist-info}/WHEEL +1 -1
  42. corehttp-1.0.0b5.dist-info/METADATA +0 -132
  43. corehttp-1.0.0b5.dist-info/RECORD +0 -52
  44. {corehttp-1.0.0b5.dist-info → corehttp-1.0.0b7.dist-info/licenses}/LICENSE +0 -0
  45. {corehttp-1.0.0b5.dist-info → corehttp-1.0.0b7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,277 @@
1
+ # ------------------------------------
2
+ # Copyright (c) Microsoft Corporation.
3
+ # Licensed under the MIT License.
4
+ # ------------------------------------
5
+ from __future__ import annotations
6
+ from contextlib import contextmanager
7
+ from contextvars import Token
8
+ from typing import Any, Optional, Dict, Sequence, cast, Callable, Iterator, TYPE_CHECKING
9
+
10
+ from opentelemetry import context as otel_context_module, trace
11
+ from opentelemetry.trace import (
12
+ Span,
13
+ SpanKind as OpenTelemetrySpanKind,
14
+ Link as OpenTelemetryLink,
15
+ StatusCode,
16
+ )
17
+ from opentelemetry.trace.propagation import get_current_span as get_current_span_otel
18
+ from opentelemetry.propagate import extract, inject
19
+
20
+ try:
21
+ from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY # type: ignore[attr-defined]
22
+ except ImportError:
23
+ _SUPPRESS_HTTP_INSTRUMENTATION_KEY = "suppress_http_instrumentation"
24
+
25
+ from ..._version import VERSION
26
+ from ._models import (
27
+ Attributes,
28
+ SpanKind as _SpanKind,
29
+ Link as _Link,
30
+ )
31
+
32
+ if TYPE_CHECKING:
33
+ from corehttp.instrumentation.tracing import Link, SpanKind
34
+
35
+
36
+ _DEFAULT_SCHEMA_URL = "https://opentelemetry.io/schemas/1.23.1"
37
+ _DEFAULT_MODULE_NAME = "corehttp"
38
+
39
+ _KIND_MAPPINGS = {
40
+ _SpanKind.CLIENT: OpenTelemetrySpanKind.CLIENT,
41
+ _SpanKind.CONSUMER: OpenTelemetrySpanKind.CONSUMER,
42
+ _SpanKind.PRODUCER: OpenTelemetrySpanKind.PRODUCER,
43
+ _SpanKind.SERVER: OpenTelemetrySpanKind.SERVER,
44
+ _SpanKind.INTERNAL: OpenTelemetrySpanKind.INTERNAL,
45
+ _SpanKind.UNSPECIFIED: OpenTelemetrySpanKind.INTERNAL,
46
+ }
47
+
48
+
49
+ class OpenTelemetryTracer:
50
+ """A tracer that uses OpenTelemetry to trace operations.
51
+
52
+ :keyword library_name: The name of the library to use in the tracer.
53
+ :paramtype library_name: str
54
+ :keyword library_version: The version of the library to use in the tracer.
55
+ :paramtype library_version: str
56
+ :keyword schema_url: Specifies the Schema URL of the emitted spans. Defaults to
57
+ "https://opentelemetry.io/schemas/1.23.1".
58
+ :paramtype schema_url: str
59
+ :keyword attributes: Attributes to add to the emitted spans.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ *,
65
+ library_name: Optional[str] = None,
66
+ library_version: Optional[str] = None,
67
+ schema_url: Optional[str] = None,
68
+ attributes: Optional[Attributes] = None,
69
+ ) -> None:
70
+ self._tracer = trace.get_tracer(
71
+ instrumenting_module_name=library_name or _DEFAULT_MODULE_NAME,
72
+ instrumenting_library_version=library_version or VERSION,
73
+ schema_url=schema_url or _DEFAULT_SCHEMA_URL,
74
+ attributes=attributes,
75
+ )
76
+
77
+ def start_span(
78
+ self,
79
+ name: str,
80
+ *,
81
+ kind: SpanKind = _SpanKind.INTERNAL,
82
+ attributes: Optional[Attributes] = None,
83
+ links: Optional[Sequence[Link]] = None,
84
+ start_time: Optional[int] = None,
85
+ context: Optional[Dict[str, Any]] = None,
86
+ ) -> Span:
87
+ """Starts a span without setting it as the current span in the context.
88
+
89
+ :param name: The name of the span
90
+ :type name: str
91
+ :keyword kind: The kind of the span. INTERNAL by default.
92
+ :paramtype kind: ~corehttp.instrumentation.tracing.SpanKind
93
+ :keyword attributes: Attributes to add to the span.
94
+ :paramtype attributes: Mapping[str, AttributeValue]
95
+ :keyword links: Links to add to the span.
96
+ :paramtype links: list[~corehttp.instrumentation.tracing.Link]
97
+ :keyword start_time: The start time of the span in nanoseconds since the epoch.
98
+ :paramtype start_time: Optional[int]
99
+ :keyword context: A dictionary of context values corresponding to the parent span. If not provided,
100
+ the current global context will be used.
101
+ :paramtype context: Optional[Dict[str, any]]
102
+ :return: The span that was started
103
+ :rtype: ~opentelemetry.trace.Span
104
+ """
105
+ otel_kind = _KIND_MAPPINGS.get(kind, OpenTelemetrySpanKind.INTERNAL)
106
+ otel_links = self._parse_links(links)
107
+
108
+ otel_context = None
109
+ if context:
110
+ otel_context = extract(context)
111
+
112
+ otel_span = self._tracer.start_span(
113
+ name,
114
+ context=otel_context,
115
+ kind=otel_kind,
116
+ attributes=attributes,
117
+ links=otel_links,
118
+ start_time=start_time,
119
+ record_exception=False,
120
+ )
121
+
122
+ return otel_span
123
+
124
+ @contextmanager
125
+ def start_as_current_span(
126
+ self,
127
+ name: str,
128
+ *,
129
+ kind: SpanKind = _SpanKind.INTERNAL,
130
+ attributes: Optional[Attributes] = None,
131
+ links: Optional[Sequence[Link]] = None,
132
+ start_time: Optional[int] = None,
133
+ context: Optional[Dict[str, Any]] = None,
134
+ end_on_exit: bool = True,
135
+ ) -> Iterator[Span]:
136
+ """Context manager that starts a span and sets it as the current span in the context.
137
+
138
+ .. code:: python
139
+
140
+ with tracer.start_as_current_span("span_name") as span:
141
+ # Do something with the span
142
+ span.set_attribute("key", "value")
143
+
144
+ :param name: The name of the span
145
+ :type name: str
146
+ :keyword kind: The kind of the span. INTERNAL by default.
147
+ :paramtype kind: ~corehttp.instrumentation.tracing.SpanKind
148
+ :keyword attributes: Attributes to add to the span.
149
+ :paramtype attributes: Optional[Attributes]
150
+ :keyword links: Links to add to the span.
151
+ :paramtype links: Optional[Sequence[Link]]
152
+ :keyword start_time: The start time of the span in nanoseconds since the epoch.
153
+ :paramtype start_time: Optional[int]
154
+ :keyword context: A dictionary of context values corresponding to the parent span. If not provided,
155
+ the current global context will be used.
156
+ :paramtype context: Optional[Dict[str, any]]
157
+ :keyword end_on_exit: Whether to end the span when exiting the context manager. Defaults to True.
158
+ :paramtype end_on_exit: bool
159
+ :return: The span that was started
160
+ :rtype: Iterator[~opentelemetry.trace.Span]
161
+ """
162
+ span = self.start_span(
163
+ name, kind=kind, attributes=attributes, links=links, start_time=start_time, context=context
164
+ )
165
+ with trace.use_span( # pylint: disable=not-context-manager
166
+ span, record_exception=False, end_on_exit=end_on_exit
167
+ ) as span:
168
+ yield span
169
+
170
+ @classmethod
171
+ @contextmanager
172
+ def use_span(cls, span: Span, *, end_on_exit: bool = True) -> Iterator[Span]:
173
+ """Context manager that takes a non-active span and activates it in the current context.
174
+
175
+ :param span: The span to set as the current span
176
+ :type span: ~opentelemetry.trace.Span
177
+ :keyword end_on_exit: Whether to end the span when exiting the context manager. Defaults to True.
178
+ :paramtype end_on_exit: bool
179
+ :return: The span that was activated.
180
+ :rtype: Iterator[~opentelemetry.trace.Span]
181
+ """
182
+ with trace.use_span( # pylint: disable=not-context-manager
183
+ span, record_exception=False, end_on_exit=end_on_exit
184
+ ) as active_span:
185
+ yield active_span
186
+
187
+ @staticmethod
188
+ def set_span_error_status(span: Span, description: Optional[str] = None) -> None:
189
+ """Set the status of a span to ERROR with the provided description, if any.
190
+
191
+ :param span: The span to set the ERROR status on.
192
+ :type span: ~opentelemetry.trace.Span
193
+ :param description: An optional description of the error.
194
+ :type description: str
195
+ """
196
+ span.set_status(StatusCode.ERROR, description=description)
197
+
198
+ def _parse_links(self, links: Optional[Sequence[Link]]) -> Optional[Sequence[OpenTelemetryLink]]:
199
+ if not links:
200
+ return None
201
+
202
+ try:
203
+ otel_links = []
204
+ for link in links:
205
+ ctx = extract(link.headers)
206
+ span_ctx = get_current_span_otel(ctx).get_span_context()
207
+ otel_links.append(OpenTelemetryLink(span_ctx, link.attributes))
208
+ return otel_links
209
+ except AttributeError:
210
+ # We will just send the links as is if it's not ~corehttp.instrumentation.tracing.Link without
211
+ # any validation assuming the user knows what they are doing.
212
+ return cast(Sequence[OpenTelemetryLink], links)
213
+
214
+ @classmethod
215
+ def get_current_span(cls) -> Span:
216
+ """Returns the current span in the context.
217
+
218
+ :return: The current span
219
+ :rtype: ~opentelemetry.trace.Span
220
+ """
221
+ return get_current_span_otel()
222
+
223
+ @classmethod
224
+ def with_current_context(cls, func: Callable) -> Callable:
225
+ """Passes the current spans to the new context the function will be run in.
226
+
227
+ :param func: The function that will be run in the new context
228
+ :type func: callable
229
+ :return: The wrapped function
230
+ :rtype: callable
231
+ """
232
+ current_context = otel_context_module.get_current()
233
+
234
+ def call_with_current_context(*args, **kwargs):
235
+ token = None
236
+ try:
237
+ token = otel_context_module.attach(current_context)
238
+ return func(*args, **kwargs)
239
+ finally:
240
+ if token is not None:
241
+ otel_context_module.detach(token)
242
+
243
+ return call_with_current_context
244
+
245
+ @classmethod
246
+ def get_trace_context(cls) -> Dict[str, str]:
247
+ """Returns the Trace Context header values associated with the current span.
248
+
249
+ These are generally the W3C Trace Context headers (i.e. "traceparent" and "tracestate").
250
+ :return: A key value pair dictionary
251
+ :rtype: dict[str, str]
252
+ """
253
+ trace_context: Dict[str, str] = {}
254
+ inject(trace_context)
255
+ return trace_context
256
+
257
+ @classmethod
258
+ def _suppress_auto_http_instrumentation(cls) -> Token:
259
+ """Enabled automatic HTTP instrumentation suppression.
260
+
261
+ Since corehttp already instruments HTTP calls, we need to suppress any automatic HTTP
262
+ instrumentation provided by other libraries to prevent duplicate spans. This has no effect if no
263
+ automatic HTTP instrumentation libraries are being used.
264
+
265
+ :return: A token that can be used to detach the suppression key from the context
266
+ :rtype: ~contextvars.Token
267
+ """
268
+ return otel_context_module.attach(otel_context_module.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True))
269
+
270
+ @classmethod
271
+ def _detach_from_context(cls, token: Token) -> None:
272
+ """Detach a token from the context.
273
+
274
+ :param token: The token to detach
275
+ :type token: ~contextvars.Token
276
+ """
277
+ otel_context_module.detach(token)
@@ -0,0 +1,31 @@
1
+ # ------------------------------------
2
+ # Copyright (c) Microsoft Corporation.
3
+ # Licensed under the MIT License.
4
+ # ------------------------------------
5
+ """Common tracing functionality for SDK libraries."""
6
+ from typing import Any, Callable
7
+
8
+ from ._tracer import get_tracer
9
+ from ...settings import settings
10
+
11
+
12
+ __all__ = [
13
+ "with_current_context",
14
+ ]
15
+
16
+
17
+ def with_current_context(func: Callable) -> Any:
18
+ """Passes the current spans to the new context the function will be run in.
19
+
20
+ :param func: The function that will be run in the new context
21
+ :type func: callable
22
+ :return: The func wrapped with correct context
23
+ :rtype: callable
24
+ """
25
+ if not settings.tracing_enabled:
26
+ return func
27
+
28
+ tracer = get_tracer()
29
+ if not tracer:
30
+ return func
31
+ return tracer.with_current_context(func)
corehttp/paging.py CHANGED
@@ -80,6 +80,13 @@ class PageIterator(Iterator[Iterator[ReturnType]]):
80
80
  return self
81
81
 
82
82
  def __next__(self) -> Iterator[ReturnType]:
83
+ """Get the next page in the iterator.
84
+
85
+ :returns: An iterator of objects in the next page.
86
+ :rtype: iterator[ReturnType]
87
+ :raises StopIteration: If there are no more pages to return.
88
+ :raises ~corehttp.exceptions.BaseError: If the request to get the next page fails.
89
+ """
83
90
  if self.continuation_token is None and self._did_a_call_already:
84
91
  raise StopIteration("End of paging")
85
92
  try:
@@ -129,6 +136,12 @@ class ItemPaged(Iterator[ReturnType]):
129
136
  return self
130
137
 
131
138
  def __next__(self) -> ReturnType:
139
+ """Get the next item in the iterator.
140
+
141
+ :returns: The next item in the iterator.
142
+ :rtype: ReturnType
143
+ :raises StopIteration: If there are no more items to return.
144
+ """
132
145
  if self._page_iterator is None:
133
146
  self._page_iterator = itertools.chain.from_iterable(self.by_page())
134
147
  return next(self._page_iterator)
corehttp/rest/_aiohttp.py CHANGED
@@ -30,7 +30,7 @@ import logging
30
30
  from itertools import groupby
31
31
  from typing import Iterator, cast, TYPE_CHECKING
32
32
  from multidict import CIMultiDict
33
- import aiohttp.client_exceptions # pylint: disable=all
33
+ import aiohttp.client_exceptions # pylint: disable=networking-import-outside-azure-core-transport
34
34
 
35
35
  from ._http_response_impl_async import AsyncHttpResponseImpl
36
36
  from ..exceptions import (
@@ -38,6 +38,7 @@ from ..exceptions import (
38
38
  ServiceRequestError,
39
39
  ServiceResponseError,
40
40
  IncompleteReadError,
41
+ ServiceResponseTimeoutError,
41
42
  )
42
43
  from ..runtime.pipeline import AsyncPipeline
43
44
  from ..transport._base_async import _ResponseStopIteration
@@ -224,7 +225,18 @@ class RestAioHttpTransportResponse(AsyncHttpResponseImpl):
224
225
  """
225
226
  if not self._content:
226
227
  self._stream_download_check()
227
- self._content = await self._internal_response.read()
228
+ try:
229
+ self._content = await self._internal_response.read()
230
+ except aiohttp.client_exceptions.ClientPayloadError as err:
231
+ # This is the case that server closes connection before we finish the reading. aiohttp library
232
+ # raises ClientPayloadError.
233
+ raise IncompleteReadError(err, error=err) from err
234
+ except aiohttp.client_exceptions.ClientResponseError as err:
235
+ raise ServiceResponseError(err, error=err) from err
236
+ except asyncio.TimeoutError as err:
237
+ raise ServiceResponseTimeoutError(err, error=err) from err
238
+ except aiohttp.client_exceptions.ClientError as err:
239
+ raise ServiceRequestError(err, error=err) from err
228
240
  await self._set_read_checks()
229
241
  return _aiohttp_content_helper(self)
230
242
 
@@ -263,7 +275,7 @@ class AioHttpStreamDownloadGenerator(collections.abc.AsyncIterator):
263
275
  self.response = response
264
276
 
265
277
  # TODO: determine if block size should be public on RestAioHttpTransportResponse.
266
- self.block_size = response._block_size # pylint: disable=protected-access
278
+ self.block_size = response._block_size
267
279
  self._decompress = decompress
268
280
  self.content_length = int(response.headers.get("Content-Length", 0))
269
281
  self._decompressor = None
@@ -272,11 +284,11 @@ class AioHttpStreamDownloadGenerator(collections.abc.AsyncIterator):
272
284
  return self.content_length
273
285
 
274
286
  async def __anext__(self):
275
- internal_response = self.response._internal_response # pylint: disable=protected-access
287
+ internal_response = self.response._internal_response
276
288
  try:
277
289
  # TODO: Determine how chunks should be read.
278
290
  # chunk = await self.response.internal_response.content.read(self.block_size)
279
- chunk = await internal_response.content.read(self.block_size) # pylint: disable=protected-access
291
+ chunk = await internal_response.content.read(self.block_size)
280
292
  if not chunk:
281
293
  raise _ResponseStopIteration()
282
294
  if not self._decompress:
@@ -300,16 +312,16 @@ class AioHttpStreamDownloadGenerator(collections.abc.AsyncIterator):
300
312
  except aiohttp.client_exceptions.ClientPayloadError as err:
301
313
  # This is the case that server closes connection before we finish the reading. aiohttp library
302
314
  # raises ClientPayloadError.
303
- _LOGGER.warning("Incomplete download: %s", err)
315
+ _LOGGER.warning("Incomplete download.")
304
316
  internal_response.close()
305
317
  raise IncompleteReadError(err, error=err) from err
306
318
  except aiohttp.client_exceptions.ClientResponseError as err:
307
319
  raise ServiceResponseError(err, error=err) from err
308
320
  except asyncio.TimeoutError as err:
309
- raise ServiceResponseError(err, error=err) from err
321
+ raise ServiceResponseTimeoutError(err, error=err) from err
310
322
  except aiohttp.client_exceptions.ClientError as err:
311
323
  raise ServiceRequestError(err, error=err) from err
312
- except Exception as err:
313
- _LOGGER.warning("Unable to stream download: %s", err)
324
+ except Exception:
325
+ _LOGGER.warning("Unable to stream download.")
314
326
  internal_response.close()
315
327
  raise
@@ -54,7 +54,7 @@ class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-inst
54
54
  :type request: ~corehttp.rest.HttpRequest
55
55
  :keyword any internal_response: The response we get directly from the transport. For example, for our requests
56
56
  transport, this will be a requests.Response.
57
- :keyword optional[int] block_size: The block size we are using in our transport
57
+ :keyword Optional[int] block_size: The block size we are using in our transport
58
58
  :keyword int status_code: The status code of the response
59
59
  :keyword str reason: The HTTP reason
60
60
  :keyword str content_type: The content type of the response
@@ -136,7 +136,7 @@ class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-inst
136
136
  def content_type(self) -> Optional[str]:
137
137
  """The content type of the response.
138
138
 
139
- :rtype: optional[str]
139
+ :rtype: Optional[str]
140
140
  :return: The content type of the response.
141
141
  """
142
142
  return self._content_type
@@ -157,7 +157,7 @@ class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-inst
157
157
  :return: The response encoding. We either return the encoding set by the user,
158
158
  or try extracting the encoding from the response's content type. If all fails,
159
159
  we return `None`.
160
- :rtype: optional[str]
160
+ :rtype: Optional[str]
161
161
  """
162
162
  try:
163
163
  return self._encoding
@@ -166,10 +166,10 @@ class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-inst
166
166
  return self._encoding
167
167
 
168
168
  @encoding.setter
169
- def encoding(self, value: str) -> None:
169
+ def encoding(self, value: Optional[str]) -> None:
170
170
  """Sets the response encoding.
171
171
 
172
- :param str value: Sets the response encoding.
172
+ :param Optional[str] value: Sets the response encoding.
173
173
  """
174
174
  self._encoding = value
175
175
  self._text = None # clear text cache
@@ -178,7 +178,7 @@ class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-inst
178
178
  def text(self, encoding: Optional[str] = None) -> str:
179
179
  """Returns the response body as a string
180
180
 
181
- :param optional[str] encoding: The encoding you want to decode the text with. Can
181
+ :param Optional[str] encoding: The encoding you want to decode the text with. Can
182
182
  also be set independently through our encoding property
183
183
  :return: The response's content decoded as a string.
184
184
  :rtype: str
@@ -246,7 +246,7 @@ class HttpResponseImpl(_HttpResponseBaseImpl, _HttpResponse):
246
246
  :type request: ~corehttp.rest.HttpRequest
247
247
  :keyword any internal_response: The response we get directly from the transport. For example, for our requests
248
248
  transport, this will be a requests.Response.
249
- :keyword optional[int] block_size: The block size we are using in our transport
249
+ :keyword Optional[int] block_size: The block size we are using in our transport
250
250
  :keyword int status_code: The status code of the response
251
251
  :keyword str reason: The HTTP reason
252
252
  :keyword str content_type: The content type of the response
@@ -292,12 +292,7 @@ class HttpResponseImpl(_HttpResponseBaseImpl, _HttpResponse):
292
292
  yield self.content[i : i + chunk_size]
293
293
  else:
294
294
  self._stream_download_check()
295
- for part in self._stream_download_generator(
296
- response=self,
297
- pipeline=None,
298
- decompress=True,
299
- ):
300
- yield part
295
+ yield from self._stream_download_generator(response=self, pipeline=None, decompress=True)
301
296
  self.close()
302
297
 
303
298
  def iter_raw(self, **kwargs) -> Iterator[bytes]:
@@ -307,6 +302,5 @@ class HttpResponseImpl(_HttpResponseBaseImpl, _HttpResponse):
307
302
  :rtype: Iterator[str]
308
303
  """
309
304
  self._stream_download_check()
310
- for part in self._stream_download_generator(response=self, pipeline=None, decompress=False):
311
- yield part
305
+ yield from self._stream_download_generator(response=self, pipeline=None, decompress=False)
312
306
  self.close()
@@ -69,6 +69,7 @@ class AsyncHttpResponseImpl(_HttpResponseBaseImpl, _AsyncHttpResponse):
69
69
 
70
70
  async def iter_raw(self, **kwargs: Any) -> AsyncIterator[bytes]:
71
71
  """Asynchronously iterates over the response's bytes. Will not decompress in the process
72
+
72
73
  :return: An async iterator of bytes from the response
73
74
  :rtype: AsyncIterator[bytes]
74
75
  """
@@ -79,6 +80,7 @@ class AsyncHttpResponseImpl(_HttpResponseBaseImpl, _AsyncHttpResponse):
79
80
 
80
81
  async def iter_bytes(self, **kwargs: Any) -> AsyncIterator[bytes]:
81
82
  """Asynchronously iterates over the response's bytes. Will decompress in the process
83
+
82
84
  :return: An async iterator of bytes from the response
83
85
  :rtype: AsyncIterator[bytes]
84
86
  """
corehttp/rest/_httpx.py CHANGED
@@ -96,10 +96,10 @@ class HttpXStreamDownloadGenerator:
96
96
  except httpx.RemoteProtocolError as ex:
97
97
  msg = ex.__str__()
98
98
  if "complete message" in msg:
99
- _LOGGER.warning("Incomplete download: %s", ex)
99
+ _LOGGER.warning("Incomplete download.")
100
100
  internal_response.close()
101
101
  raise IncompleteReadError(ex, error=ex) from ex
102
- _LOGGER.warning("Unable to stream download: %s", ex)
102
+ _LOGGER.warning("Unable to stream download.")
103
103
  internal_response.close()
104
104
  raise HttpResponseError(ex, error=ex) from ex
105
105
  except httpx.DecodingError as ex:
@@ -108,8 +108,8 @@ class HttpXStreamDownloadGenerator:
108
108
  raise DecodeError("Failed to decode.", error=ex) from ex
109
109
  except httpx.RequestError as err:
110
110
  raise ServiceRequestError(err, error=err) from err
111
- except Exception as err:
112
- _LOGGER.warning("Unable to stream download: %s", err)
111
+ except Exception:
112
+ _LOGGER.warning("Unable to stream download.")
113
113
  internal_response.close()
114
114
  raise
115
115
 
@@ -177,7 +177,7 @@ class AsyncHttpXStreamDownloadGenerator(AsyncIterator):
177
177
  return self
178
178
 
179
179
  async def __anext__(self):
180
- internal_response = self.response._internal_response # pylint: disable=protected-access
180
+ internal_response = self.response._internal_response
181
181
  try:
182
182
  return await self.iter_content_func.__anext__()
183
183
  except StopAsyncIteration:
@@ -186,10 +186,10 @@ class AsyncHttpXStreamDownloadGenerator(AsyncIterator):
186
186
  except httpx.RemoteProtocolError as ex:
187
187
  msg = ex.__str__()
188
188
  if "complete message" in msg:
189
- _LOGGER.warning("Incomplete download: %s", ex)
189
+ _LOGGER.warning("Incomplete download.")
190
190
  await internal_response.aclose()
191
191
  raise IncompleteReadError(ex, error=ex) from ex
192
- _LOGGER.warning("Unable to stream download: %s", ex)
192
+ _LOGGER.warning("Unable to stream download.")
193
193
  await internal_response.aclose()
194
194
  raise HttpResponseError(ex, error=ex) from ex
195
195
  except httpx.DecodingError as ex:
@@ -198,7 +198,7 @@ class AsyncHttpXStreamDownloadGenerator(AsyncIterator):
198
198
  raise DecodeError("Failed to decode.", error=ex) from ex
199
199
  except httpx.RequestError as err:
200
200
  raise ServiceRequestError(err, error=err) from err
201
- except Exception as err:
202
- _LOGGER.warning("Unable to stream download: %s", err)
201
+ except Exception:
202
+ _LOGGER.warning("Unable to stream download.")
203
203
  await internal_response.aclose()
204
204
  raise
@@ -27,8 +27,8 @@ from __future__ import annotations
27
27
  import logging
28
28
  import collections.abc as collections
29
29
  from typing import TYPE_CHECKING, Any
30
- import requests # pylint: disable=all
31
- from requests.structures import CaseInsensitiveDict # pylint: disable=all
30
+ import requests # pylint: disable=networking-import-outside-azure-core-transport
31
+ from requests.structures import CaseInsensitiveDict # pylint: disable=networking-import-outside-azure-core-transport
32
32
  from urllib3.exceptions import (
33
33
  DecodeError as CoreDecodeError,
34
34
  ReadTimeoutError,
@@ -38,8 +38,8 @@ from urllib3.exceptions import (
38
38
  from ..runtime.pipeline import Pipeline
39
39
  from ._http_response_impl import _HttpResponseBaseImpl, HttpResponseImpl
40
40
  from ..exceptions import (
41
- ServiceRequestError,
42
41
  ServiceResponseError,
42
+ ServiceResponseTimeoutError,
43
43
  IncompleteReadError,
44
44
  HttpResponseError,
45
45
  DecodeError,
@@ -122,7 +122,7 @@ class StreamDownloadGenerator:
122
122
  self.response = response
123
123
 
124
124
  # TODO: determine if block size should be public on RestTransportResponse.
125
- self.block_size = response._block_size # pylint: disable=protected-access
125
+ self.block_size = response._block_size
126
126
  decompress = kwargs.pop("decompress", True)
127
127
  if len(kwargs) > 0:
128
128
  raise TypeError("Got an unexpected keyword argument: {}".format(list(kwargs.keys())[0]))
@@ -156,14 +156,22 @@ class StreamDownloadGenerator:
156
156
  except requests.exceptions.ChunkedEncodingError as err:
157
157
  msg = err.__str__()
158
158
  if "IncompleteRead" in msg:
159
- _LOGGER.warning("Incomplete download: %s", err)
159
+ _LOGGER.warning("Incomplete download.")
160
160
  internal_response.close()
161
161
  raise IncompleteReadError(err, error=err) from err
162
- _LOGGER.warning("Unable to stream download: %s", err)
162
+ _LOGGER.warning("Unable to stream download.")
163
163
  internal_response.close()
164
164
  raise HttpResponseError(err, error=err) from err
165
+ except requests.ConnectionError as err:
166
+ internal_response.close()
167
+ if err.args and isinstance(err.args[0], ReadTimeoutError):
168
+ raise ServiceResponseTimeoutError(err, error=err) from err
169
+ raise ServiceResponseError(err, error=err) from err
170
+ except requests.RequestException as err:
171
+ internal_response.close()
172
+ raise ServiceResponseError(err, error=err) from err
165
173
  except Exception as err:
166
- _LOGGER.warning("Unable to stream download: %s", err)
174
+ _LOGGER.warning("Unable to stream download.")
167
175
  internal_response.close()
168
176
  raise
169
177
 
@@ -172,14 +180,13 @@ def _read_raw_stream(response, chunk_size=1):
172
180
  # Special case for urllib3.
173
181
  if hasattr(response.raw, "stream"):
174
182
  try:
175
- for chunk in response.raw.stream(chunk_size, decode_content=False):
176
- yield chunk
183
+ yield from response.raw.stream(chunk_size, decode_content=False)
177
184
  except ProtocolError as e:
178
185
  raise ServiceResponseError(e, error=e) from e
179
186
  except CoreDecodeError as e:
180
187
  raise DecodeError(e, error=e) from e
181
188
  except ReadTimeoutError as e:
182
- raise ServiceRequestError(e, error=e) from e
189
+ raise ServiceResponseTimeoutError(e, error=e) from e
183
190
  else:
184
191
  # Standard file-like object.
185
192
  while True: