Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/azure/core/pipeline/transport/_base.py: 33%

249 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-07 06:33 +0000

1# -------------------------------------------------------------------------- 

2# 

3# Copyright (c) Microsoft Corporation. All rights reserved. 

4# 

5# The MIT License (MIT) 

6# 

7# Permission is hereby granted, free of charge, to any person obtaining a copy 

8# of this software and associated documentation files (the ""Software""), to 

9# deal in the Software without restriction, including without limitation the 

10# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 

11# sell copies of the Software, and to permit persons to whom the Software is 

12# furnished to do so, subject to the following conditions: 

13# 

14# The above copyright notice and this permission notice shall be included in 

15# all copies or substantial portions of the Software. 

16# 

17# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 

22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 

23# IN THE SOFTWARE. 

24# 

25# -------------------------------------------------------------------------- 

26from __future__ import annotations 

27import abc 

28from email.message import Message 

29import json 

30import logging 

31import time 

32import copy 

33from urllib.parse import urlparse 

34import xml.etree.ElementTree as ET 

35 

36from typing import ( 

37 Generic, 

38 TypeVar, 

39 IO, 

40 Union, 

41 Any, 

42 Mapping, 

43 Optional, 

44 Tuple, 

45 Iterator, 

46 Type, 

47 Dict, 

48 List, 

49 Sequence, 

50 MutableMapping, 

51 ContextManager, 

52 TYPE_CHECKING, 

53) 

54 

55from http.client import HTTPResponse as _HTTPResponse 

56 

57from azure.core.exceptions import HttpResponseError 

58from azure.core.pipeline.policies import SansIOHTTPPolicy 

59from ...utils._utils import case_insensitive_dict 

60from ...utils._pipeline_transport_rest_shared import ( 

61 _format_parameters_helper, 

62 _prepare_multipart_body_helper, 

63 _serialize_request, 

64 _format_data_helper, 

65 BytesIOSocket, 

66 _decode_parts_helper, 

67 _get_raw_parts_helper, 

68 _parts_helper, 

69) 

70 

71 

72HTTPResponseType = TypeVar("HTTPResponseType") 

73HTTPRequestType = TypeVar("HTTPRequestType") 

74DataType = Union[bytes, str, Dict[str, Union[str, int]]] 

75 

76if TYPE_CHECKING: 

77 # We need a transport to define a pipeline, this "if" avoid a circular import 

78 from azure.core.pipeline import Pipeline 

79 

80_LOGGER = logging.getLogger(__name__) 

81 

82binary_type = str 

83 

84 

85def _format_url_section(template, **kwargs: Dict[str, str]) -> str: 

86 """String format the template with the kwargs, auto-skip sections of the template that are NOT in the kwargs. 

87 

88 By default in Python, "format" will raise a KeyError if a template element is not found. Here the section between 

89 the slashes will be removed from the template instead. 

90 

91 This is used for API like Storage, where when Swagger has template section not defined as parameter. 

92 

93 :param str template: a string template to fill 

94 :rtype: str 

95 :returns: Template completed 

96 """ 

97 last_template = template 

98 components = template.split("/") 

99 while components: 

100 try: 

101 return template.format(**kwargs) 

102 except KeyError as key: 

103 formatted_components = template.split("/") 

104 components = [c for c in formatted_components if "{{{}}}".format(key.args[0]) not in c] 

105 template = "/".join(components) 

106 if last_template == template: 

107 raise ValueError( 

108 f"The value provided for the url part '{template}' was incorrect, and resulted in an invalid url" 

109 ) from key 

110 last_template = template 

111 return last_template 

112 

113 

114def _urljoin(base_url: str, stub_url: str) -> str: 

115 """Append to end of base URL without losing query parameters. 

116 

117 :param str base_url: The base URL. 

118 :param str stub_url: Section to append to the end of the URL path. 

119 :returns: The updated URL. 

120 :rtype: str 

121 """ 

122 parsed_base_url = urlparse(base_url) 

123 

124 # Can't use "urlparse" on a partial url, we get incorrect parsing for things like 

125 # document:build?format=html&api-version=2019-05-01 

126 split_url = stub_url.split("?", 1) 

127 stub_url_path = split_url.pop(0) 

128 stub_url_query = split_url.pop() if split_url else None 

129 

130 # Note that _replace is a public API named that way to avoid conflicts in namedtuple 

131 # https://docs.python.org/3/library/collections.html?highlight=namedtuple#collections.namedtuple 

132 parsed_base_url = parsed_base_url._replace( 

133 path=parsed_base_url.path.rstrip("/") + "/" + stub_url_path, 

134 ) 

135 if stub_url_query: 

136 query_params = [stub_url_query] 

137 if parsed_base_url.query: 

138 query_params.insert(0, parsed_base_url.query) 

139 parsed_base_url = parsed_base_url._replace(query="&".join(query_params)) 

140 return parsed_base_url.geturl() 

141 

142 

143class HttpTransport(ContextManager["HttpTransport"], abc.ABC, Generic[HTTPRequestType, HTTPResponseType]): 

144 """An http sender ABC.""" 

145 

146 @abc.abstractmethod 

147 def send(self, request: HTTPRequestType, **kwargs: Any) -> HTTPResponseType: 

148 """Send the request using this HTTP sender. 

149 

150 :param request: The pipeline request object 

151 :type request: ~azure.core.transport.HTTPRequest 

152 :return: The pipeline response object. 

153 :rtype: ~azure.core.pipeline.transport.HttpResponse 

154 """ 

155 

156 @abc.abstractmethod 

157 def open(self) -> None: 

158 """Assign new session if one does not already exist.""" 

159 

160 @abc.abstractmethod 

161 def close(self) -> None: 

162 """Close the session if it is not externally owned.""" 

163 

164 def sleep(self, duration: float) -> None: 

165 """Sleep for the specified duration. 

166 

167 You should always ask the transport to sleep, and not call directly 

168 the stdlib. This is mostly important in async, as the transport 

169 may not use asyncio but other implementations like trio and they have their own 

170 way to sleep, but to keep design 

171 consistent, it's cleaner to always ask the transport to sleep and let the transport 

172 implementor decide how to do it. 

173 

174 :param float duration: The number of seconds to sleep. 

175 """ 

176 time.sleep(duration) 

177 

178 

179class HttpRequest: 

180 """Represents an HTTP request. 

181 

182 URL can be given without query parameters, to be added later using "format_parameters". 

183 

184 :param str method: HTTP method (GET, HEAD, etc.) 

185 :param str url: At least complete scheme/host/path 

186 :param dict[str,str] headers: HTTP headers 

187 :param files: Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart 

188 encoding upload. ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple 

189 ``('filename', fileobj, 'content_type')`` or a 4-tuple 

190 ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string 

191 defining the content type of the given file and ``custom_headers`` 

192 a dict-like object containing additional headers to add for the file. 

193 :type files: dict[str, tuple[str, IO, str, dict]] or dict[str, IO] 

194 :param data: Body to be sent. 

195 :type data: bytes or dict (for form) 

196 """ 

197 

198 def __init__( 

199 self, 

200 method: str, 

201 url: str, 

202 headers: Optional[Mapping[str, str]] = None, 

203 files: Optional[Any] = None, 

204 data: Optional[DataType] = None, 

205 ) -> None: 

206 self.method = method 

207 self.url = url 

208 self.headers: MutableMapping[str, str] = case_insensitive_dict(headers) 

209 self.files: Optional[Any] = files 

210 self.data: Optional[DataType] = data 

211 self.multipart_mixed_info: Optional[Tuple[Sequence[Any], Sequence[Any], Optional[str], Dict[str, Any]]] = None 

212 

213 def __repr__(self) -> str: 

214 return "<HttpRequest [{}], url: '{}'>".format(self.method, self.url) 

215 

216 def __deepcopy__(self, memo: Optional[Dict[int, Any]] = None) -> "HttpRequest": 

217 try: 

218 data = copy.deepcopy(self.body, memo) 

219 files = copy.deepcopy(self.files, memo) 

220 request = HttpRequest(self.method, self.url, self.headers, files, data) 

221 request.multipart_mixed_info = self.multipart_mixed_info 

222 return request 

223 except (ValueError, TypeError): 

224 return copy.copy(self) 

225 

226 @property 

227 def query(self) -> Dict[str, str]: 

228 """The query parameters of the request as a dict. 

229 

230 :rtype: dict[str, str] 

231 :return: The query parameters of the request as a dict. 

232 """ 

233 query = urlparse(self.url).query 

234 if query: 

235 return {p[0]: p[-1] for p in [p.partition("=") for p in query.split("&")]} 

236 return {} 

237 

238 @property 

239 def body(self) -> Optional[DataType]: 

240 """Alias to data. 

241 

242 :rtype: bytes or str or dict or None 

243 :return: The body of the request. 

244 """ 

245 return self.data 

246 

247 @body.setter 

248 def body(self, value: Optional[DataType]) -> None: 

249 self.data = value 

250 

251 @staticmethod 

252 def _format_data(data: Union[str, IO]) -> Union[Tuple[None, str], Tuple[Optional[str], IO, str]]: 

253 """Format field data according to whether it is a stream or 

254 a string for a form-data request. 

255 

256 :param data: The request field data. 

257 :type data: str or file-like object. 

258 :rtype: tuple[str, IO, str] or tuple[None, str] 

259 :return: A tuple of (data name, data IO, "application/octet-stream") or (None, data str) 

260 """ 

261 return _format_data_helper(data) 

262 

263 def format_parameters(self, params: Dict[str, str]) -> None: 

264 """Format parameters into a valid query string. 

265 It's assumed all parameters have already been quoted as 

266 valid URL strings. 

267 

268 :param dict params: A dictionary of parameters. 

269 """ 

270 return _format_parameters_helper(self, params) 

271 

272 def set_streamed_data_body(self, data: Any) -> None: 

273 """Set a streamable data body. 

274 

275 :param data: The request field data. 

276 :type data: stream or generator or asyncgenerator 

277 """ 

278 if not isinstance(data, binary_type) and not any( 

279 hasattr(data, attr) for attr in ["read", "__iter__", "__aiter__"] 

280 ): 

281 raise TypeError("A streamable data source must be an open file-like object or iterable.") 

282 self.data = data 

283 self.files = None 

284 

285 def set_text_body(self, data: str) -> None: 

286 """Set a text as body of the request. 

287 

288 :param data: A text to send as body. 

289 :type data: str 

290 """ 

291 if data is None: 

292 self.data = None 

293 else: 

294 self.data = data 

295 self.headers["Content-Length"] = str(len(self.data)) 

296 self.files = None 

297 

298 def set_xml_body(self, data: Any) -> None: 

299 """Set an XML element tree as the body of the request. 

300 

301 :param data: The request field data. 

302 :type data: XML node 

303 """ 

304 if data is None: 

305 self.data = None 

306 else: 

307 bytes_data: bytes = ET.tostring(data, encoding="utf8") 

308 self.data = bytes_data.replace(b"encoding='utf8'", b"encoding='utf-8'") 

309 self.headers["Content-Length"] = str(len(self.data)) 

310 self.files = None 

311 

312 def set_json_body(self, data: Any) -> None: 

313 """Set a JSON-friendly object as the body of the request. 

314 

315 :param data: A JSON serializable object 

316 :type data: dict 

317 """ 

318 if data is None: 

319 self.data = None 

320 else: 

321 self.data = json.dumps(data) 

322 self.headers["Content-Length"] = str(len(self.data)) 

323 self.files = None 

324 

325 def set_formdata_body(self, data: Optional[Dict[str, str]] = None) -> None: 

326 """Set form-encoded data as the body of the request. 

327 

328 :param data: The request field data. 

329 :type data: dict 

330 """ 

331 if data is None: 

332 data = {} 

333 content_type = self.headers.pop("Content-Type", None) if self.headers else None 

334 

335 if content_type and content_type.lower() == "application/x-www-form-urlencoded": 

336 self.data = {f: d for f, d in data.items() if d is not None} 

337 self.files = None 

338 else: # Assume "multipart/form-data" 

339 self.files = {f: self._format_data(d) for f, d in data.items() if d is not None} 

340 self.data = None 

341 

342 def set_bytes_body(self, data: bytes) -> None: 

343 """Set generic bytes as the body of the request. 

344 

345 Will set content-length. 

346 

347 :param data: The request field data. 

348 :type data: bytes 

349 """ 

350 if data: 

351 self.headers["Content-Length"] = str(len(data)) 

352 self.data = data 

353 self.files = None 

354 

355 def set_multipart_mixed( 

356 self, 

357 *requests: "HttpRequest", 

358 policies: Optional[List[SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]]] = None, 

359 boundary: Optional[str] = None, 

360 **kwargs: Any, 

361 ) -> None: 

362 """Set the part of a multipart/mixed. 

363 

364 Only supported args for now are HttpRequest objects. 

365 

366 boundary is optional, and one will be generated if you don't provide one. 

367 Note that no verification are made on the boundary, this is considered advanced 

368 enough so you know how to respect RFC1341 7.2.1 and provide a correct boundary. 

369 

370 Any additional kwargs will be passed into the pipeline context for per-request policy 

371 configuration. 

372 

373 :param requests: The requests to add to the multipart/mixed 

374 :type requests: ~azure.core.pipeline.transport.HttpRequest 

375 :keyword list[SansIOHTTPPolicy] policies: SansIOPolicy to apply at preparation time 

376 :keyword str boundary: Optional boundary 

377 """ 

378 policies = policies or [] 

379 self.multipart_mixed_info = ( 

380 requests, 

381 policies, 

382 boundary, 

383 kwargs, 

384 ) 

385 

386 def prepare_multipart_body(self, content_index: int = 0) -> int: 

387 """Will prepare the body of this request according to the multipart information. 

388 

389 This call assumes the on_request policies have been applied already in their 

390 correct context (sync/async) 

391 

392 Does nothing if "set_multipart_mixed" was never called. 

393 

394 :param int content_index: The current index of parts within the batch message. 

395 :returns: The updated index after all parts in this request have been added. 

396 :rtype: int 

397 """ 

398 return _prepare_multipart_body_helper(self, content_index) 

399 

400 def serialize(self) -> bytes: 

401 """Serialize this request using application/http spec. 

402 

403 :rtype: bytes 

404 :return: The requests serialized as HTTP low-level message in bytes. 

405 """ 

406 return _serialize_request(self) 

407 

408 

409class _HttpResponseBase: 

410 """Represent a HTTP response. 

411 

412 No body is defined here on purpose, since async pipeline 

413 will provide async ways to access the body 

414 Full in-memory using "body" as bytes. 

415 

416 :param request: The request. 

417 :type request: ~azure.core.pipeline.transport.HttpRequest 

418 :param internal_response: The object returned from the HTTP library. 

419 :type internal_response: any 

420 :param int block_size: Defaults to 4096 bytes. 

421 """ 

422 

423 def __init__( 

424 self, 

425 request: "HttpRequest", 

426 internal_response: Any, 

427 block_size: Optional[int] = None, 

428 ) -> None: 

429 self.request: HttpRequest = request 

430 self.internal_response = internal_response 

431 # This is actually never None, and set by all implementations after the call to 

432 # __init__ of this class. This class is also a legacy impl, so it's risky to change it 

433 # for low benefits The new "rest" implementation does define correctly status_code 

434 # as non-optional. 

435 self.status_code: int = None # type: ignore 

436 self.headers: MutableMapping[str, str] = {} 

437 self.reason: Optional[str] = None 

438 self.content_type: Optional[str] = None 

439 self.block_size: int = block_size or 4096 # Default to same as Requests 

440 

441 def body(self) -> bytes: 

442 """Return the whole body as bytes in memory. 

443 

444 Sync implementer should load the body in memory if they can. 

445 Async implementer should rely on async load_body to have been called first. 

446 

447 :rtype: bytes 

448 :return: The whole body as bytes in memory. 

449 """ 

450 raise NotImplementedError() 

451 

452 def text(self, encoding: Optional[str] = None) -> str: 

453 """Return the whole body as a string. 

454 

455 .. seealso:: ~body() 

456 

457 :param str encoding: The encoding to apply. If None, use "utf-8" with BOM parsing (utf-8-sig). 

458 Implementation can be smarter if they want (using headers or chardet). 

459 :rtype: str 

460 :return: The whole body as a string. 

461 """ 

462 if encoding == "utf-8" or encoding is None: 

463 encoding = "utf-8-sig" 

464 return self.body().decode(encoding) 

465 

466 def _decode_parts( 

467 self, 

468 message: Message, 

469 http_response_type: Type["_HttpResponseBase"], 

470 requests: Sequence[HttpRequest], 

471 ) -> List["HttpResponse"]: 

472 """Rebuild an HTTP response from pure string. 

473 

474 :param ~email.message.Message message: The HTTP message as an email object 

475 :param type http_response_type: The type of response to return 

476 :param list[HttpRequest] requests: The requests that were batched together 

477 :rtype: list[HttpResponse] 

478 :return: The list of HttpResponse 

479 """ 

480 return _decode_parts_helper(self, message, http_response_type, requests, _deserialize_response) 

481 

482 def _get_raw_parts( 

483 self, http_response_type: Optional[Type["_HttpResponseBase"]] = None 

484 ) -> Iterator["HttpResponse"]: 

485 """Assuming this body is multipart, return the iterator or parts. 

486 

487 If parts are application/http use http_response_type or HttpClientTransportResponse 

488 as envelope. 

489 

490 :param type http_response_type: The type of response to return 

491 :rtype: iterator[HttpResponse] 

492 :return: The iterator of HttpResponse 

493 """ 

494 return _get_raw_parts_helper(self, http_response_type or HttpClientTransportResponse) 

495 

496 def raise_for_status(self) -> None: 

497 """Raises an HttpResponseError if the response has an error status code. 

498 If response is good, does nothing. 

499 """ 

500 if not self.status_code or self.status_code >= 400: 

501 raise HttpResponseError(response=self) 

502 

503 def __repr__(self) -> str: 

504 content_type_str = ", Content-Type: {}".format(self.content_type) if self.content_type else "" 

505 return "<{}: {} {}{}>".format(type(self).__name__, self.status_code, self.reason, content_type_str) 

506 

507 

508class HttpResponse(_HttpResponseBase): # pylint: disable=abstract-method 

509 def stream_download(self, pipeline: Pipeline[HttpRequest, "HttpResponse"], **kwargs: Any) -> Iterator[bytes]: 

510 """Generator for streaming request body data. 

511 

512 Should be implemented by sub-classes if streaming download 

513 is supported. 

514 

515 :param pipeline: The pipeline object 

516 :type pipeline: ~azure.core.pipeline.Pipeline 

517 :rtype: iterator[bytes] 

518 :return: The generator of bytes connected to the socket 

519 """ 

520 raise NotImplementedError("stream_download is not implemented.") 

521 

522 def parts(self) -> Iterator["HttpResponse"]: 

523 """Assuming the content-type is multipart/mixed, will return the parts as an iterator. 

524 

525 :rtype: iterator[HttpResponse] 

526 :return: The iterator of HttpResponse if request was multipart/mixed 

527 :raises ValueError: If the content is not multipart/mixed 

528 """ 

529 return _parts_helper(self) 

530 

531 

532class _HttpClientTransportResponse(_HttpResponseBase): 

533 """Create a HTTPResponse from an http.client response. 

534 

535 Body will NOT be read by the constructor. Call "body()" to load the body in memory if necessary. 

536 

537 :param HttpRequest request: The request. 

538 :param httpclient_response: The object returned from an HTTP(S)Connection from http.client 

539 :type httpclient_response: http.client.HTTPResponse 

540 """ 

541 

542 def __init__(self, request, httpclient_response): 

543 super(_HttpClientTransportResponse, self).__init__(request, httpclient_response) 

544 self.status_code = httpclient_response.status 

545 self.headers = case_insensitive_dict(httpclient_response.getheaders()) 

546 self.reason = httpclient_response.reason 

547 self.content_type = self.headers.get("Content-Type") 

548 self.data = None 

549 

550 def body(self): 

551 if self.data is None: 

552 self.data = self.internal_response.read() 

553 return self.data 

554 

555 

556class HttpClientTransportResponse(_HttpClientTransportResponse, HttpResponse): # pylint: disable=abstract-method 

557 """Create a HTTPResponse from an http.client response. 

558 

559 Body will NOT be read by the constructor. Call "body()" to load the body in memory if necessary. 

560 """ 

561 

562 

563def _deserialize_response(http_response_as_bytes, http_request, http_response_type=HttpClientTransportResponse): 

564 """Deserialize a HTTPResponse from a string. 

565 

566 :param bytes http_response_as_bytes: The HTTP response as bytes. 

567 :param HttpRequest http_request: The request to store in the response. 

568 :param type http_response_type: The type of response to return 

569 :rtype: HttpResponse 

570 :return: The HTTP response from those low-level bytes. 

571 """ 

572 local_socket = BytesIOSocket(http_response_as_bytes) 

573 response = _HTTPResponse(local_socket, method=http_request.method) 

574 response.begin() 

575 return http_response_type(http_request, response) 

576 

577 

578class PipelineClientBase: 

579 """Base class for pipeline clients. 

580 

581 :param str base_url: URL for the request. 

582 """ 

583 

584 def __init__(self, base_url: str): 

585 self._base_url = base_url 

586 

587 def _request( 

588 self, 

589 method: str, 

590 url: str, 

591 params: Optional[Dict[str, str]], 

592 headers: Optional[Dict[str, str]], 

593 content: Any, 

594 form_content: Optional[Dict[str, Any]], 

595 stream_content: Any, 

596 ) -> HttpRequest: 

597 """Create HttpRequest object. 

598 

599 If content is not None, guesses will be used to set the right body: 

600 - If content is an XML tree, will serialize as XML 

601 - If content-type starts by "text/", set the content as text 

602 - Else, try JSON serialization 

603 

604 :param str method: HTTP method (GET, HEAD, etc.) 

605 :param str url: URL for the request. 

606 :param dict params: URL query parameters. 

607 :param dict headers: Headers 

608 :param content: The body content 

609 :type content: bytes or str or dict 

610 :param dict form_content: Form content 

611 :param stream_content: The body content as a stream 

612 :type stream_content: stream or generator or asyncgenerator 

613 :return: An HttpRequest object 

614 :rtype: ~azure.core.pipeline.transport.HttpRequest 

615 """ 

616 request = HttpRequest(method, self.format_url(url)) 

617 

618 if params: 

619 request.format_parameters(params) 

620 

621 if headers: 

622 request.headers.update(headers) 

623 

624 if content is not None: 

625 content_type = request.headers.get("Content-Type") 

626 if isinstance(content, ET.Element): 

627 request.set_xml_body(content) 

628 # https://github.com/Azure/azure-sdk-for-python/issues/12137 

629 # A string is valid JSON, make the difference between text 

630 # and a plain JSON string. 

631 # Content-Type is a good indicator of intent from user 

632 elif content_type and content_type.startswith("text/"): 

633 request.set_text_body(content) 

634 else: 

635 try: 

636 request.set_json_body(content) 

637 except TypeError: 

638 request.data = content 

639 

640 if form_content: 

641 request.set_formdata_body(form_content) 

642 elif stream_content: 

643 request.set_streamed_data_body(stream_content) 

644 

645 return request 

646 

647 def format_url(self, url_template: str, **kwargs: Any) -> str: 

648 """Format request URL with the client base URL, unless the 

649 supplied URL is already absolute. 

650 

651 Note that both the base url and the template url can contain query parameters. 

652 

653 :param str url_template: The request URL to be formatted if necessary. 

654 :rtype: str 

655 :return: The formatted URL. 

656 """ 

657 url = _format_url_section(url_template, **kwargs) 

658 if url: 

659 parsed = urlparse(url) 

660 if not parsed.scheme or not parsed.netloc: 

661 url = url.lstrip("/") 

662 try: 

663 base = self._base_url.format(**kwargs).rstrip("/") 

664 except KeyError as key: 

665 err_msg = "The value provided for the url part {} was incorrect, and resulted in an invalid url" 

666 raise ValueError(err_msg.format(key.args[0])) from key 

667 

668 url = _urljoin(base, url) 

669 else: 

670 url = self._base_url.format(**kwargs) 

671 return url 

672 

673 def get( 

674 self, 

675 url: str, 

676 params: Optional[Dict[str, str]] = None, 

677 headers: Optional[Dict[str, str]] = None, 

678 content: Any = None, 

679 form_content: Optional[Dict[str, Any]] = None, 

680 ) -> "HttpRequest": 

681 """Create a GET request object. 

682 

683 :param str url: The request URL. 

684 :param dict params: Request URL parameters. 

685 :param dict headers: Headers 

686 :param content: The body content 

687 :type content: bytes or str or dict 

688 :param dict form_content: Form content 

689 :return: An HttpRequest object 

690 :rtype: ~azure.core.pipeline.transport.HttpRequest 

691 """ 

692 request = self._request("GET", url, params, headers, content, form_content, None) 

693 request.method = "GET" 

694 return request 

695 

696 def put( 

697 self, 

698 url: str, 

699 params: Optional[Dict[str, str]] = None, 

700 headers: Optional[Dict[str, str]] = None, 

701 content: Any = None, 

702 form_content: Optional[Dict[str, Any]] = None, 

703 stream_content: Any = None, 

704 ) -> HttpRequest: 

705 """Create a PUT request object. 

706 

707 :param str url: The request URL. 

708 :param dict params: Request URL parameters. 

709 :param dict headers: Headers 

710 :param content: The body content 

711 :type content: bytes or str or dict 

712 :param dict form_content: Form content 

713 :param stream_content: The body content as a stream 

714 :type stream_content: stream or generator or asyncgenerator 

715 :return: An HttpRequest object 

716 :rtype: ~azure.core.pipeline.transport.HttpRequest 

717 """ 

718 request = self._request("PUT", url, params, headers, content, form_content, stream_content) 

719 return request 

720 

721 def post( 

722 self, 

723 url: str, 

724 params: Optional[Dict[str, str]] = None, 

725 headers: Optional[Dict[str, str]] = None, 

726 content: Any = None, 

727 form_content: Optional[Dict[str, Any]] = None, 

728 stream_content: Any = None, 

729 ) -> HttpRequest: 

730 """Create a POST request object. 

731 

732 :param str url: The request URL. 

733 :param dict params: Request URL parameters. 

734 :param dict headers: Headers 

735 :param content: The body content 

736 :type content: bytes or str or dict 

737 :param dict form_content: Form content 

738 :param stream_content: The body content as a stream 

739 :type stream_content: stream or generator or asyncgenerator 

740 :return: An HttpRequest object 

741 :rtype: ~azure.core.pipeline.transport.HttpRequest 

742 """ 

743 request = self._request("POST", url, params, headers, content, form_content, stream_content) 

744 return request 

745 

746 def head( 

747 self, 

748 url: str, 

749 params: Optional[Dict[str, str]] = None, 

750 headers: Optional[Dict[str, str]] = None, 

751 content: Any = None, 

752 form_content: Optional[Dict[str, Any]] = None, 

753 stream_content: Any = None, 

754 ) -> HttpRequest: 

755 """Create a HEAD request object. 

756 

757 :param str url: The request URL. 

758 :param dict params: Request URL parameters. 

759 :param dict headers: Headers 

760 :param content: The body content 

761 :type content: bytes or str or dict 

762 :param dict form_content: Form content 

763 :param stream_content: The body content as a stream 

764 :type stream_content: stream or generator or asyncgenerator 

765 :return: An HttpRequest object 

766 :rtype: ~azure.core.pipeline.transport.HttpRequest 

767 """ 

768 request = self._request("HEAD", url, params, headers, content, form_content, stream_content) 

769 return request 

770 

771 def patch( 

772 self, 

773 url: str, 

774 params: Optional[Dict[str, str]] = None, 

775 headers: Optional[Dict[str, str]] = None, 

776 content: Any = None, 

777 form_content: Optional[Dict[str, Any]] = None, 

778 stream_content: Any = None, 

779 ) -> HttpRequest: 

780 """Create a PATCH request object. 

781 

782 :param str url: The request URL. 

783 :param dict params: Request URL parameters. 

784 :param dict headers: Headers 

785 :param content: The body content 

786 :type content: bytes or str or dict 

787 :param dict form_content: Form content 

788 :param stream_content: The body content as a stream 

789 :type stream_content: stream or generator or asyncgenerator 

790 :return: An HttpRequest object 

791 :rtype: ~azure.core.pipeline.transport.HttpRequest 

792 """ 

793 request = self._request("PATCH", url, params, headers, content, form_content, stream_content) 

794 return request 

795 

796 def delete( 

797 self, 

798 url: str, 

799 params: Optional[Dict[str, str]] = None, 

800 headers: Optional[Dict[str, str]] = None, 

801 content: Any = None, 

802 form_content: Optional[Dict[str, Any]] = None, 

803 ) -> HttpRequest: 

804 """Create a DELETE request object. 

805 

806 :param str url: The request URL. 

807 :param dict params: Request URL parameters. 

808 :param dict headers: Headers 

809 :param content: The body content 

810 :type content: bytes or str or dict 

811 :param dict form_content: Form content 

812 :return: An HttpRequest object 

813 :rtype: ~azure.core.pipeline.transport.HttpRequest 

814 """ 

815 request = self._request("DELETE", url, params, headers, content, form_content, None) 

816 return request 

817 

818 def merge( 

819 self, 

820 url: str, 

821 params: Optional[Dict[str, str]] = None, 

822 headers: Optional[Dict[str, str]] = None, 

823 content: Any = None, 

824 form_content: Optional[Dict[str, Any]] = None, 

825 ) -> HttpRequest: 

826 """Create a MERGE request object. 

827 

828 :param str url: The request URL. 

829 :param dict params: Request URL parameters. 

830 :param dict headers: Headers 

831 :param content: The body content 

832 :type content: bytes or str or dict 

833 :param dict form_content: Form content 

834 :return: An HttpRequest object 

835 :rtype: ~azure.core.pipeline.transport.HttpRequest 

836 """ 

837 request = self._request("MERGE", url, params, headers, content, form_content, None) 

838 return request 

839 

840 def options( 

841 self, # pylint: disable=unused-argument 

842 url: str, 

843 params: Optional[Dict[str, str]] = None, 

844 headers: Optional[Dict[str, str]] = None, 

845 *, 

846 content: Optional[Union[bytes, str, Dict[Any, Any]]] = None, 

847 form_content: Optional[Dict[Any, Any]] = None, 

848 **kwargs: Any, 

849 ) -> HttpRequest: 

850 """Create a OPTIONS request object. 

851 

852 :param str url: The request URL. 

853 :param dict params: Request URL parameters. 

854 :param dict headers: Headers 

855 :keyword content: The body content 

856 :type content: bytes or str or dict 

857 :keyword dict form_content: Form content 

858 :return: An HttpRequest object 

859 :rtype: ~azure.core.pipeline.transport.HttpRequest 

860 """ 

861 request = self._request("OPTIONS", url, params, headers, content, form_content, None) 

862 return request