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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

251 statements  

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 from azure.core.rest._helpers import FileContent 

80 

81_LOGGER = logging.getLogger(__name__) 

82 

83binary_type = str 

84 

85 

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

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

88 

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

90 the slashes will be removed from the template instead. 

91 

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

93 

94 :param str template: a string template to fill 

95 :rtype: str 

96 :returns: Template completed 

97 """ 

98 last_template = template 

99 components = template.split("/") 

100 while components: 

101 try: 

102 return template.format(**kwargs) 

103 except KeyError as key: 

104 formatted_components = template.split("/") 

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

106 template = "/".join(components) 

107 if last_template == template: 

108 raise ValueError( 

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

110 ) from key 

111 last_template = template 

112 return last_template 

113 

114 

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

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

117 

118 :param str base_url: The base URL. 

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

120 :returns: The updated URL. 

121 :rtype: str 

122 """ 

123 parsed_base_url = urlparse(base_url) 

124 

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

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

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

128 stub_url_path = split_url.pop(0) 

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

130 

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

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

133 parsed_base_url = parsed_base_url._replace( 

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

135 ) 

136 if stub_url_query: 

137 query_params = [stub_url_query] 

138 if parsed_base_url.query: 

139 query_params.insert(0, parsed_base_url.query) 

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

141 return parsed_base_url.geturl() 

142 

143 

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

145 """An http sender ABC.""" 

146 

147 @abc.abstractmethod 

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

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

150 

151 :param request: The pipeline request object 

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

153 :return: The pipeline response object. 

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

155 """ 

156 

157 @abc.abstractmethod 

158 def open(self) -> None: 

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

160 

161 @abc.abstractmethod 

162 def close(self) -> None: 

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

164 

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

166 """Sleep for the specified duration. 

167 

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

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

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

171 way to sleep, but to keep design 

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

173 implementor decide how to do it. 

174 

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

176 """ 

177 time.sleep(duration) 

178 

179 

180class HttpRequest: 

181 """Represents an HTTP request. 

182 

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

184 

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

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

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

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

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

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

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

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

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

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

195 :param data: Body to be sent. 

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

197 """ 

198 

199 def __init__( 

200 self, 

201 method: str, 

202 url: str, 

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

204 files: Optional[Any] = None, 

205 data: Optional[DataType] = None, 

206 ) -> None: 

207 self.method = method 

208 self.url = url 

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

210 self.files: Optional[Any] = files 

211 self.data: Optional[DataType] = data 

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

213 

214 def __repr__(self) -> str: 

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

216 

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

218 try: 

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

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

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

222 request.multipart_mixed_info = self.multipart_mixed_info 

223 return request 

224 except (ValueError, TypeError): 

225 return copy.copy(self) 

226 

227 @property 

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

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

230 

231 :rtype: dict[str, str] 

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

233 """ 

234 query = urlparse(self.url).query 

235 if query: 

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

237 return {} 

238 

239 @property 

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

241 """Alias to data. 

242 

243 :rtype: bytes or str or dict or None 

244 :return: The body of the request. 

245 """ 

246 return self.data 

247 

248 @body.setter 

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

250 self.data = value 

251 

252 @staticmethod 

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

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

255 a string for a form-data request. 

256 

257 :param data: The request field data. 

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

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

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

261 """ 

262 return _format_data_helper(data) 

263 

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

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

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

267 valid URL strings. 

268 

269 :param dict params: A dictionary of parameters. 

270 """ 

271 return _format_parameters_helper(self, params) 

272 

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

274 """Set a streamable data body. 

275 

276 :param data: The request field data. 

277 :type data: stream or generator or asyncgenerator 

278 """ 

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

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

281 ): 

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

283 self.data = data 

284 self.files = None 

285 

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

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

288 

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

290 :type data: str 

291 """ 

292 if data is None: 

293 self.data = None 

294 else: 

295 self.data = data 

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

297 self.files = None 

298 

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

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

301 

302 :param data: The request field data. 

303 :type data: XML node 

304 """ 

305 if data is None: 

306 self.data = None 

307 else: 

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

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

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

311 self.files = None 

312 

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

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

315 

316 :param data: A JSON serializable object 

317 :type data: dict 

318 """ 

319 if data is None: 

320 self.data = None 

321 else: 

322 self.data = json.dumps(data) 

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

324 self.files = None 

325 

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

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

328 

329 :param data: The request field data. 

330 :type data: dict 

331 """ 

332 if data is None: 

333 data = {} 

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

335 

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

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

338 self.files = None 

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

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

341 self.data = None 

342 

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

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

345 

346 Will set content-length. 

347 

348 :param data: The request field data. 

349 :type data: bytes 

350 """ 

351 if data: 

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

353 self.data = data 

354 self.files = None 

355 

356 def set_multipart_mixed( 

357 self, 

358 *requests: "HttpRequest", 

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

360 boundary: Optional[str] = None, 

361 **kwargs: Any, 

362 ) -> None: 

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

364 

365 Only supported args for now are HttpRequest objects. 

366 

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

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

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

370 

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

372 configuration. 

373 

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

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

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

377 :keyword str boundary: Optional boundary 

378 """ 

379 policies = policies or [] 

380 self.multipart_mixed_info = ( 

381 requests, 

382 policies, 

383 boundary, 

384 kwargs, 

385 ) 

386 

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

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

389 

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

391 correct context (sync/async) 

392 

393 Does nothing if "set_multipart_mixed" was never called. 

394 

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

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

397 :rtype: int 

398 """ 

399 return _prepare_multipart_body_helper(self, content_index) 

400 

401 def serialize(self) -> bytes: 

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

403 

404 :rtype: bytes 

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

406 """ 

407 return _serialize_request(self) 

408 

409 

410class _HttpResponseBase: 

411 """Represent a HTTP response. 

412 

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

414 will provide async ways to access the body 

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

416 

417 :param request: The request. 

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

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

420 :type internal_response: any 

421 :param int block_size: Defaults to 4096 bytes. 

422 """ 

423 

424 def __init__( 

425 self, 

426 request: "HttpRequest", 

427 internal_response: Any, 

428 block_size: Optional[int] = None, 

429 ) -> None: 

430 self.request: HttpRequest = request 

431 self.internal_response = internal_response 

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

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

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

435 # as non-optional. 

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

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

438 self.reason: Optional[str] = None 

439 self.content_type: Optional[str] = None 

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

441 

442 def body(self) -> bytes: 

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

444 

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

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

447 

448 :rtype: bytes 

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

450 """ 

451 raise NotImplementedError() 

452 

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

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

455 

456 .. seealso:: ~body() 

457 

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

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

460 :rtype: str 

461 :return: The whole body as a string. 

462 """ 

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

464 encoding = "utf-8-sig" 

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

466 

467 def _decode_parts( 

468 self, 

469 message: Message, 

470 http_response_type: Type["_HttpResponseBase"], 

471 requests: Sequence[HttpRequest], 

472 ) -> List["HttpResponse"]: 

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

474 

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

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

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

478 :rtype: list[HttpResponse] 

479 :return: The list of HttpResponse 

480 """ 

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

482 

483 def _get_raw_parts( 

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

485 ) -> Iterator["HttpResponse"]: 

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

487 

488 If parts are application/http use http_response_type or HttpClientTransportResponse 

489 as envelope. 

490 

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

492 :rtype: iterator[HttpResponse] 

493 :return: The iterator of HttpResponse 

494 """ 

495 return _get_raw_parts_helper(self, http_response_type or HttpClientTransportResponse) 

496 

497 def raise_for_status(self) -> None: 

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

499 If response is good, does nothing. 

500 """ 

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

502 raise HttpResponseError(response=self) 

503 

504 def __repr__(self) -> str: 

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

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

507 

508 

509class HttpResponse(_HttpResponseBase): 

510 """Represent a HTTP response.""" 

511 

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

513 """Generator for streaming request body data. 

514 

515 Should be implemented by sub-classes if streaming download 

516 is supported. 

517 

518 :param pipeline: The pipeline object 

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

520 :rtype: iterator[bytes] 

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

522 """ 

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

524 

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

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

527 

528 :rtype: iterator[HttpResponse] 

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

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

531 """ 

532 return _parts_helper(self) 

533 

534 

535class _HttpClientTransportResponse(_HttpResponseBase): 

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

537 

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

539 

540 :param HttpRequest request: The request. 

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

542 :type httpclient_response: http.client.HTTPResponse 

543 """ 

544 

545 def __init__(self, request, httpclient_response): 

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

547 self.status_code = httpclient_response.status 

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

549 self.reason = httpclient_response.reason 

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

551 self.data = None 

552 

553 def body(self): 

554 if self.data is None: 

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

556 return self.data 

557 

558 

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

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

561 

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

563 """ 

564 

565 

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

567 """Deserialize a HTTPResponse from a string. 

568 

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

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

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

572 :rtype: HttpResponse 

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

574 """ 

575 local_socket = BytesIOSocket(http_response_as_bytes) 

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

577 response.begin() 

578 return http_response_type(http_request, response) 

579 

580 

581class PipelineClientBase: 

582 """Base class for pipeline clients. 

583 

584 :param str base_url: URL for the request. 

585 """ 

586 

587 def __init__(self, base_url: str): 

588 self._base_url = base_url 

589 

590 def _request( 

591 self, 

592 method: str, 

593 url: str, 

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

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

596 content: Any, 

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

598 stream_content: Any, 

599 ) -> HttpRequest: 

600 """Create HttpRequest object. 

601 

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

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

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

605 - Else, try JSON serialization 

606 

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

608 :param str url: URL for the request. 

609 :param dict params: URL query parameters. 

610 :param dict headers: Headers 

611 :param content: The body content 

612 :type content: bytes or str or dict 

613 :param dict form_content: Form content 

614 :param stream_content: The body content as a stream 

615 :type stream_content: stream or generator or asyncgenerator 

616 :return: An HttpRequest object 

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

618 """ 

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

620 

621 if params: 

622 request.format_parameters(params) 

623 

624 if headers: 

625 request.headers.update(headers) 

626 

627 if content is not None: 

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

629 if isinstance(content, ET.Element): 

630 request.set_xml_body(content) 

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

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

633 # and a plain JSON string. 

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

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

636 request.set_text_body(content) 

637 else: 

638 try: 

639 request.set_json_body(content) 

640 except TypeError: 

641 request.data = content 

642 

643 if form_content: 

644 request.set_formdata_body(form_content) 

645 elif stream_content: 

646 request.set_streamed_data_body(stream_content) 

647 

648 return request 

649 

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

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

652 supplied URL is already absolute. 

653 

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

655 

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

657 :rtype: str 

658 :return: The formatted URL. 

659 """ 

660 url = _format_url_section(url_template, **kwargs) 

661 if url: 

662 parsed = urlparse(url) 

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

664 url = url.lstrip("/") 

665 try: 

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

667 except KeyError as key: 

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

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

670 

671 url = _urljoin(base, url) 

672 else: 

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

674 return url 

675 

676 def get( 

677 self, 

678 url: str, 

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

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

681 content: Any = None, 

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

683 ) -> "HttpRequest": 

684 """Create a GET request object. 

685 

686 :param str url: The request URL. 

687 :param dict params: Request URL parameters. 

688 :param dict headers: Headers 

689 :param content: The body content 

690 :type content: bytes or str or dict 

691 :param dict form_content: Form content 

692 :return: An HttpRequest object 

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

694 """ 

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

696 request.method = "GET" 

697 return request 

698 

699 def put( 

700 self, 

701 url: str, 

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

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

704 content: Any = None, 

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

706 stream_content: Any = None, 

707 ) -> HttpRequest: 

708 """Create a PUT request object. 

709 

710 :param str url: The request URL. 

711 :param dict params: Request URL parameters. 

712 :param dict headers: Headers 

713 :param content: The body content 

714 :type content: bytes or str or dict 

715 :param dict form_content: Form content 

716 :param stream_content: The body content as a stream 

717 :type stream_content: stream or generator or asyncgenerator 

718 :return: An HttpRequest object 

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

720 """ 

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

722 return request 

723 

724 def post( 

725 self, 

726 url: str, 

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

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

729 content: Any = None, 

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

731 stream_content: Any = None, 

732 ) -> HttpRequest: 

733 """Create a POST request object. 

734 

735 :param str url: The request URL. 

736 :param dict params: Request URL parameters. 

737 :param dict headers: Headers 

738 :param content: The body content 

739 :type content: bytes or str or dict 

740 :param dict form_content: Form content 

741 :param stream_content: The body content as a stream 

742 :type stream_content: stream or generator or asyncgenerator 

743 :return: An HttpRequest object 

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

745 """ 

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

747 return request 

748 

749 def head( 

750 self, 

751 url: str, 

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

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

754 content: Any = None, 

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

756 stream_content: Any = None, 

757 ) -> HttpRequest: 

758 """Create a HEAD request object. 

759 

760 :param str url: The request URL. 

761 :param dict params: Request URL parameters. 

762 :param dict headers: Headers 

763 :param content: The body content 

764 :type content: bytes or str or dict 

765 :param dict form_content: Form content 

766 :param stream_content: The body content as a stream 

767 :type stream_content: stream or generator or asyncgenerator 

768 :return: An HttpRequest object 

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

770 """ 

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

772 return request 

773 

774 def patch( 

775 self, 

776 url: str, 

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

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

779 content: Any = None, 

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

781 stream_content: Any = None, 

782 ) -> HttpRequest: 

783 """Create a PATCH request object. 

784 

785 :param str url: The request URL. 

786 :param dict params: Request URL parameters. 

787 :param dict headers: Headers 

788 :param content: The body content 

789 :type content: bytes or str or dict 

790 :param dict form_content: Form content 

791 :param stream_content: The body content as a stream 

792 :type stream_content: stream or generator or asyncgenerator 

793 :return: An HttpRequest object 

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

795 """ 

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

797 return request 

798 

799 def delete( 

800 self, 

801 url: str, 

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

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

804 content: Any = None, 

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

806 ) -> HttpRequest: 

807 """Create a DELETE request object. 

808 

809 :param str url: The request URL. 

810 :param dict params: Request URL parameters. 

811 :param dict headers: Headers 

812 :param content: The body content 

813 :type content: bytes or str or dict 

814 :param dict form_content: Form content 

815 :return: An HttpRequest object 

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

817 """ 

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

819 return request 

820 

821 def merge( 

822 self, 

823 url: str, 

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

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

826 content: Any = None, 

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

828 ) -> HttpRequest: 

829 """Create a MERGE request object. 

830 

831 :param str url: The request URL. 

832 :param dict params: Request URL parameters. 

833 :param dict headers: Headers 

834 :param content: The body content 

835 :type content: bytes or str or dict 

836 :param dict form_content: Form content 

837 :return: An HttpRequest object 

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

839 """ 

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

841 return request 

842 

843 def options( 

844 self, # pylint: disable=unused-argument 

845 url: str, 

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

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

848 *, 

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

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

851 **kwargs: Any, 

852 ) -> HttpRequest: 

853 """Create a OPTIONS request object. 

854 

855 :param str url: The request URL. 

856 :param dict params: Request URL parameters. 

857 :param dict headers: Headers 

858 :keyword content: The body content 

859 :type content: bytes or str or dict 

860 :keyword dict form_content: Form content 

861 :return: An HttpRequest object 

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

863 """ 

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

865 return request