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.lstrip("/"),
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 try:
666 base = self._base_url.format(**kwargs)
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