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.
- corehttp/_version.py +1 -1
- corehttp/credentials.py +66 -25
- corehttp/exceptions.py +7 -6
- corehttp/instrumentation/__init__.py +9 -0
- corehttp/instrumentation/tracing/__init__.py +14 -0
- corehttp/instrumentation/tracing/_decorator.py +189 -0
- corehttp/instrumentation/tracing/_models.py +72 -0
- corehttp/instrumentation/tracing/_tracer.py +69 -0
- corehttp/instrumentation/tracing/opentelemetry.py +277 -0
- corehttp/instrumentation/tracing/utils.py +31 -0
- corehttp/paging.py +13 -0
- corehttp/rest/_aiohttp.py +21 -9
- corehttp/rest/_http_response_impl.py +9 -15
- corehttp/rest/_http_response_impl_async.py +2 -0
- corehttp/rest/_httpx.py +9 -9
- corehttp/rest/_requests_basic.py +17 -10
- corehttp/rest/_rest_py3.py +6 -10
- corehttp/runtime/pipeline/__init__.py +5 -9
- corehttp/runtime/pipeline/_base.py +3 -2
- corehttp/runtime/pipeline/_base_async.py +6 -8
- corehttp/runtime/pipeline/_tools.py +18 -2
- corehttp/runtime/pipeline/_tools_async.py +2 -4
- corehttp/runtime/policies/__init__.py +2 -0
- corehttp/runtime/policies/_authentication.py +76 -24
- corehttp/runtime/policies/_authentication_async.py +66 -21
- corehttp/runtime/policies/_distributed_tracing.py +169 -0
- corehttp/runtime/policies/_retry.py +8 -12
- corehttp/runtime/policies/_retry_async.py +5 -9
- corehttp/runtime/policies/_universal.py +15 -11
- corehttp/serialization.py +237 -3
- corehttp/settings.py +59 -0
- corehttp/transport/_base.py +1 -3
- corehttp/transport/_base_async.py +1 -3
- corehttp/transport/aiohttp/_aiohttp.py +41 -16
- corehttp/transport/requests/_bigger_block_size_http_adapters.py +1 -1
- corehttp/transport/requests/_requests_basic.py +33 -18
- corehttp/utils/_enum_meta.py +1 -1
- corehttp/utils/_utils.py +2 -1
- corehttp-1.0.0b7.dist-info/METADATA +196 -0
- corehttp-1.0.0b7.dist-info/RECORD +61 -0
- {corehttp-1.0.0b5.dist-info → corehttp-1.0.0b7.dist-info}/WHEEL +1 -1
- corehttp-1.0.0b5.dist-info/METADATA +0 -132
- corehttp-1.0.0b5.dist-info/RECORD +0 -52
- {corehttp-1.0.0b5.dist-info → corehttp-1.0.0b7.dist-info/licenses}/LICENSE +0 -0
- {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=
|
|
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
|
-
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
313
|
-
_LOGGER.warning("Unable to stream download
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
112
|
-
_LOGGER.warning("Unable to stream download
|
|
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
|
|
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
|
|
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
|
|
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
|
|
202
|
-
_LOGGER.warning("Unable to stream download
|
|
201
|
+
except Exception:
|
|
202
|
+
_LOGGER.warning("Unable to stream download.")
|
|
203
203
|
await internal_response.aclose()
|
|
204
204
|
raise
|
corehttp/rest/_requests_basic.py
CHANGED
|
@@ -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=
|
|
31
|
-
from requests.structures import CaseInsensitiveDict # pylint: disable=
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
189
|
+
raise ServiceResponseTimeoutError(e, error=e) from e
|
|
183
190
|
else:
|
|
184
191
|
# Standard file-like object.
|
|
185
192
|
while True:
|