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

252 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 if stub_url_path: 

134 parsed_base_url = parsed_base_url._replace( 

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

136 ) 

137 if stub_url_query: 

138 query_params = [stub_url_query] 

139 if parsed_base_url.query: 

140 query_params.insert(0, parsed_base_url.query) 

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

142 return parsed_base_url.geturl() 

143 

144 

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

146 """An http sender ABC.""" 

147 

148 @abc.abstractmethod 

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

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

151 

152 :param request: The pipeline request object 

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

154 :return: The pipeline response object. 

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

156 """ 

157 

158 @abc.abstractmethod 

159 def open(self) -> None: 

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

161 

162 @abc.abstractmethod 

163 def close(self) -> None: 

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

165 

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

167 """Sleep for the specified duration. 

168 

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

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

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

172 way to sleep, but to keep design 

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

174 implementor decide how to do it. 

175 

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

177 """ 

178 time.sleep(duration) 

179 

180 

181class HttpRequest: 

182 """Represents an HTTP request. 

183 

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

185 

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

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

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

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

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

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

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

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

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

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

196 :param data: Body to be sent. 

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

198 """ 

199 

200 def __init__( 

201 self, 

202 method: str, 

203 url: str, 

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

205 files: Optional[Any] = None, 

206 data: Optional[DataType] = None, 

207 ) -> None: 

208 self.method = method 

209 self.url = url 

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

211 self.files: Optional[Any] = files 

212 self.data: Optional[DataType] = data 

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

214 

215 def __repr__(self) -> str: 

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

217 

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

219 try: 

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

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

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

223 request.multipart_mixed_info = self.multipart_mixed_info 

224 return request 

225 except (ValueError, TypeError): 

226 return copy.copy(self) 

227 

228 @property 

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

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

231 

232 :rtype: dict[str, str] 

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

234 """ 

235 query = urlparse(self.url).query 

236 if query: 

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

238 return {} 

239 

240 @property 

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

242 """Alias to data. 

243 

244 :rtype: bytes or str or dict or None 

245 :return: The body of the request. 

246 """ 

247 return self.data 

248 

249 @body.setter 

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

251 self.data = value 

252 

253 @staticmethod 

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

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

256 a string for a form-data request. 

257 

258 :param data: The request field data. 

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

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

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

262 """ 

263 return _format_data_helper(data) 

264 

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

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

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

268 valid URL strings. 

269 

270 :param dict params: A dictionary of parameters. 

271 """ 

272 return _format_parameters_helper(self, params) 

273 

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

275 """Set a streamable data body. 

276 

277 :param data: The request field data. 

278 :type data: stream or generator or asyncgenerator 

279 """ 

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

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

282 ): 

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

284 self.data = data 

285 self.files = None 

286 

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

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

289 

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

291 :type data: str 

292 """ 

293 if data is None: 

294 self.data = None 

295 else: 

296 self.data = data 

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

298 self.files = None 

299 

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

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

302 

303 :param data: The request field data. 

304 :type data: XML node 

305 """ 

306 if data is None: 

307 self.data = None 

308 else: 

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

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

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

312 self.files = None 

313 

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

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

316 

317 :param data: A JSON serializable object 

318 :type data: dict 

319 """ 

320 if data is None: 

321 self.data = None 

322 else: 

323 self.data = json.dumps(data) 

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

325 self.files = None 

326 

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

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

329 

330 :param data: The request field data. 

331 :type data: dict 

332 """ 

333 if data is None: 

334 data = {} 

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

336 

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

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

339 self.files = None 

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

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

342 self.data = None 

343 

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

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

346 

347 Will set content-length. 

348 

349 :param data: The request field data. 

350 :type data: bytes 

351 """ 

352 if data: 

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

354 self.data = data 

355 self.files = None 

356 

357 def set_multipart_mixed( 

358 self, 

359 *requests: "HttpRequest", 

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

361 boundary: Optional[str] = None, 

362 **kwargs: Any, 

363 ) -> None: 

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

365 

366 Only supported args for now are HttpRequest objects. 

367 

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

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

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

371 

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

373 configuration. 

374 

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

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

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

378 :keyword str boundary: Optional boundary 

379 """ 

380 policies = policies or [] 

381 self.multipart_mixed_info = ( 

382 requests, 

383 policies, 

384 boundary, 

385 kwargs, 

386 ) 

387 

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

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

390 

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

392 correct context (sync/async) 

393 

394 Does nothing if "set_multipart_mixed" was never called. 

395 

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

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

398 :rtype: int 

399 """ 

400 return _prepare_multipart_body_helper(self, content_index) 

401 

402 def serialize(self) -> bytes: 

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

404 

405 :rtype: bytes 

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

407 """ 

408 return _serialize_request(self) 

409 

410 

411class _HttpResponseBase: 

412 """Represent a HTTP response. 

413 

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

415 will provide async ways to access the body 

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

417 

418 :param request: The request. 

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

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

421 :type internal_response: any 

422 :param int block_size: Defaults to 4096 bytes. 

423 """ 

424 

425 def __init__( 

426 self, 

427 request: "HttpRequest", 

428 internal_response: Any, 

429 block_size: Optional[int] = None, 

430 ) -> None: 

431 self.request: HttpRequest = request 

432 self.internal_response = internal_response 

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

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

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

436 # as non-optional. 

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

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

439 self.reason: Optional[str] = None 

440 self.content_type: Optional[str] = None 

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

442 

443 def body(self) -> bytes: 

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

445 

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

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

448 

449 :rtype: bytes 

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

451 """ 

452 raise NotImplementedError() 

453 

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

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

456 

457 .. seealso:: ~body() 

458 

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

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

461 :rtype: str 

462 :return: The whole body as a string. 

463 """ 

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

465 encoding = "utf-8-sig" 

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

467 

468 def _decode_parts( 

469 self, 

470 message: Message, 

471 http_response_type: Type["_HttpResponseBase"], 

472 requests: Sequence[HttpRequest], 

473 ) -> List["HttpResponse"]: 

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

475 

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

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

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

479 :rtype: list[HttpResponse] 

480 :return: The list of HttpResponse 

481 """ 

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

483 

484 def _get_raw_parts( 

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

486 ) -> Iterator["HttpResponse"]: 

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

488 

489 If parts are application/http use http_response_type or HttpClientTransportResponse 

490 as envelope. 

491 

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

493 :rtype: iterator[HttpResponse] 

494 :return: The iterator of HttpResponse 

495 """ 

496 return _get_raw_parts_helper(self, http_response_type or HttpClientTransportResponse) 

497 

498 def raise_for_status(self) -> None: 

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

500 If response is good, does nothing. 

501 """ 

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

503 raise HttpResponseError(response=self) 

504 

505 def __repr__(self) -> str: 

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

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

508 

509 

510class HttpResponse(_HttpResponseBase): 

511 """Represent a HTTP response.""" 

512 

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

514 """Generator for streaming request body data. 

515 

516 Should be implemented by sub-classes if streaming download 

517 is supported. 

518 

519 :param pipeline: The pipeline object 

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

521 :rtype: iterator[bytes] 

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

523 """ 

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

525 

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

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

528 

529 :rtype: iterator[HttpResponse] 

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

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

532 """ 

533 return _parts_helper(self) 

534 

535 

536class _HttpClientTransportResponse(_HttpResponseBase): 

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

538 

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

540 

541 :param HttpRequest request: The request. 

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

543 :type httpclient_response: http.client.HTTPResponse 

544 """ 

545 

546 def __init__(self, request, httpclient_response): 

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

548 self.status_code = httpclient_response.status 

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

550 self.reason = httpclient_response.reason 

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

552 self.data = None 

553 

554 def body(self): 

555 if self.data is None: 

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

557 return self.data 

558 

559 

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

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

562 

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

564 """ 

565 

566 

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

568 """Deserialize a HTTPResponse from a string. 

569 

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

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

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

573 :rtype: HttpResponse 

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

575 """ 

576 local_socket = BytesIOSocket(http_response_as_bytes) 

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

578 response.begin() 

579 return http_response_type(http_request, response) 

580 

581 

582class PipelineClientBase: 

583 """Base class for pipeline clients. 

584 

585 :param str base_url: URL for the request. 

586 """ 

587 

588 def __init__(self, base_url: str): 

589 self._base_url = base_url 

590 

591 def _request( 

592 self, 

593 method: str, 

594 url: str, 

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

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

597 content: Any, 

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

599 stream_content: Any, 

600 ) -> HttpRequest: 

601 """Create HttpRequest object. 

602 

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

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

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

606 - Else, try JSON serialization 

607 

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

609 :param str url: URL for the request. 

610 :param dict params: URL query parameters. 

611 :param dict headers: Headers 

612 :param content: The body content 

613 :type content: bytes or str or dict 

614 :param dict form_content: Form content 

615 :param stream_content: The body content as a stream 

616 :type stream_content: stream or generator or asyncgenerator 

617 :return: An HttpRequest object 

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

619 """ 

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

621 

622 if params: 

623 request.format_parameters(params) 

624 

625 if headers: 

626 request.headers.update(headers) 

627 

628 if content is not None: 

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

630 if isinstance(content, ET.Element): 

631 request.set_xml_body(content) 

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

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

634 # and a plain JSON string. 

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

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

637 request.set_text_body(content) 

638 else: 

639 try: 

640 request.set_json_body(content) 

641 except TypeError: 

642 request.data = content 

643 

644 if form_content: 

645 request.set_formdata_body(form_content) 

646 elif stream_content: 

647 request.set_streamed_data_body(stream_content) 

648 

649 return request 

650 

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

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

653 supplied URL is already absolute. 

654 

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

656 

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

658 :rtype: str 

659 :return: The formatted URL. 

660 """ 

661 url = _format_url_section(url_template, **kwargs) 

662 if url: 

663 parsed = urlparse(url) 

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

665 url = url.lstrip("/") 

666 try: 

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

668 except KeyError as key: 

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

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

671 

672 url = _urljoin(base, url) 

673 else: 

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

675 return url 

676 

677 def get( 

678 self, 

679 url: str, 

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

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

682 content: Any = None, 

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

684 ) -> "HttpRequest": 

685 """Create a GET request object. 

686 

687 :param str url: The request URL. 

688 :param dict params: Request URL parameters. 

689 :param dict headers: Headers 

690 :param content: The body content 

691 :type content: bytes or str or dict 

692 :param dict form_content: Form content 

693 :return: An HttpRequest object 

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

695 """ 

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

697 request.method = "GET" 

698 return request 

699 

700 def put( 

701 self, 

702 url: str, 

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

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

705 content: Any = None, 

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

707 stream_content: Any = None, 

708 ) -> HttpRequest: 

709 """Create a PUT request object. 

710 

711 :param str url: The request URL. 

712 :param dict params: Request URL parameters. 

713 :param dict headers: Headers 

714 :param content: The body content 

715 :type content: bytes or str or dict 

716 :param dict form_content: Form content 

717 :param stream_content: The body content as a stream 

718 :type stream_content: stream or generator or asyncgenerator 

719 :return: An HttpRequest object 

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

721 """ 

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

723 return request 

724 

725 def post( 

726 self, 

727 url: str, 

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

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

730 content: Any = None, 

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

732 stream_content: Any = None, 

733 ) -> HttpRequest: 

734 """Create a POST request object. 

735 

736 :param str url: The request URL. 

737 :param dict params: Request URL parameters. 

738 :param dict headers: Headers 

739 :param content: The body content 

740 :type content: bytes or str or dict 

741 :param dict form_content: Form content 

742 :param stream_content: The body content as a stream 

743 :type stream_content: stream or generator or asyncgenerator 

744 :return: An HttpRequest object 

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

746 """ 

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

748 return request 

749 

750 def head( 

751 self, 

752 url: str, 

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

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

755 content: Any = None, 

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

757 stream_content: Any = None, 

758 ) -> HttpRequest: 

759 """Create a HEAD request object. 

760 

761 :param str url: The request URL. 

762 :param dict params: Request URL parameters. 

763 :param dict headers: Headers 

764 :param content: The body content 

765 :type content: bytes or str or dict 

766 :param dict form_content: Form content 

767 :param stream_content: The body content as a stream 

768 :type stream_content: stream or generator or asyncgenerator 

769 :return: An HttpRequest object 

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

771 """ 

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

773 return request 

774 

775 def patch( 

776 self, 

777 url: str, 

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

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

780 content: Any = None, 

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

782 stream_content: Any = None, 

783 ) -> HttpRequest: 

784 """Create a PATCH request object. 

785 

786 :param str url: The request URL. 

787 :param dict params: Request URL parameters. 

788 :param dict headers: Headers 

789 :param content: The body content 

790 :type content: bytes or str or dict 

791 :param dict form_content: Form content 

792 :param stream_content: The body content as a stream 

793 :type stream_content: stream or generator or asyncgenerator 

794 :return: An HttpRequest object 

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

796 """ 

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

798 return request 

799 

800 def delete( 

801 self, 

802 url: str, 

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

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

805 content: Any = None, 

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

807 ) -> HttpRequest: 

808 """Create a DELETE request object. 

809 

810 :param str url: The request URL. 

811 :param dict params: Request URL parameters. 

812 :param dict headers: Headers 

813 :param content: The body content 

814 :type content: bytes or str or dict 

815 :param dict form_content: Form content 

816 :return: An HttpRequest object 

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

818 """ 

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

820 return request 

821 

822 def merge( 

823 self, 

824 url: str, 

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

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

827 content: Any = None, 

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

829 ) -> HttpRequest: 

830 """Create a MERGE request object. 

831 

832 :param str url: The request URL. 

833 :param dict params: Request URL parameters. 

834 :param dict headers: Headers 

835 :param content: The body content 

836 :type content: bytes or str or dict 

837 :param dict form_content: Form content 

838 :return: An HttpRequest object 

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

840 """ 

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

842 return request 

843 

844 def options( 

845 self, # pylint: disable=unused-argument 

846 url: str, 

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

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

849 *, 

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

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

852 **kwargs: Any, 

853 ) -> HttpRequest: 

854 """Create a OPTIONS request object. 

855 

856 :param str url: The request URL. 

857 :param dict params: Request URL parameters. 

858 :param dict headers: Headers 

859 :keyword content: The body content 

860 :type content: bytes or str or dict 

861 :keyword dict form_content: Form content 

862 :return: An HttpRequest object 

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

864 """ 

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

866 return request