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): # pylint: disable=abstract-method
510 def stream_download(self, pipeline: Pipeline[HttpRequest, "HttpResponse"], **kwargs: Any) -> Iterator[bytes]:
511 """Generator for streaming request body data.
512
513 Should be implemented by sub-classes if streaming download
514 is supported.
515
516 :param pipeline: The pipeline object
517 :type pipeline: ~azure.core.pipeline.Pipeline
518 :rtype: iterator[bytes]
519 :return: The generator of bytes connected to the socket
520 """
521 raise NotImplementedError("stream_download is not implemented.")
522
523 def parts(self) -> Iterator["HttpResponse"]:
524 """Assuming the content-type is multipart/mixed, will return the parts as an iterator.
525
526 :rtype: iterator[HttpResponse]
527 :return: The iterator of HttpResponse if request was multipart/mixed
528 :raises ValueError: If the content is not multipart/mixed
529 """
530 return _parts_helper(self)
531
532
533class _HttpClientTransportResponse(_HttpResponseBase):
534 """Create a HTTPResponse from an http.client response.
535
536 Body will NOT be read by the constructor. Call "body()" to load the body in memory if necessary.
537
538 :param HttpRequest request: The request.
539 :param httpclient_response: The object returned from an HTTP(S)Connection from http.client
540 :type httpclient_response: http.client.HTTPResponse
541 """
542
543 def __init__(self, request, httpclient_response):
544 super(_HttpClientTransportResponse, self).__init__(request, httpclient_response)
545 self.status_code = httpclient_response.status
546 self.headers = case_insensitive_dict(httpclient_response.getheaders())
547 self.reason = httpclient_response.reason
548 self.content_type = self.headers.get("Content-Type")
549 self.data = None
550
551 def body(self):
552 if self.data is None:
553 self.data = self.internal_response.read()
554 return self.data
555
556
557class HttpClientTransportResponse(_HttpClientTransportResponse, HttpResponse): # pylint: disable=abstract-method
558 """Create a HTTPResponse from an http.client response.
559
560 Body will NOT be read by the constructor. Call "body()" to load the body in memory if necessary.
561 """
562
563
564def _deserialize_response(http_response_as_bytes, http_request, http_response_type=HttpClientTransportResponse):
565 """Deserialize a HTTPResponse from a string.
566
567 :param bytes http_response_as_bytes: The HTTP response as bytes.
568 :param HttpRequest http_request: The request to store in the response.
569 :param type http_response_type: The type of response to return
570 :rtype: HttpResponse
571 :return: The HTTP response from those low-level bytes.
572 """
573 local_socket = BytesIOSocket(http_response_as_bytes)
574 response = _HTTPResponse(local_socket, method=http_request.method)
575 response.begin()
576 return http_response_type(http_request, response)
577
578
579class PipelineClientBase:
580 """Base class for pipeline clients.
581
582 :param str base_url: URL for the request.
583 """
584
585 def __init__(self, base_url: str):
586 self._base_url = base_url
587
588 def _request(
589 self,
590 method: str,
591 url: str,
592 params: Optional[Dict[str, str]],
593 headers: Optional[Dict[str, str]],
594 content: Any,
595 form_content: Optional[Dict[str, Any]],
596 stream_content: Any,
597 ) -> HttpRequest:
598 """Create HttpRequest object.
599
600 If content is not None, guesses will be used to set the right body:
601 - If content is an XML tree, will serialize as XML
602 - If content-type starts by "text/", set the content as text
603 - Else, try JSON serialization
604
605 :param str method: HTTP method (GET, HEAD, etc.)
606 :param str url: URL for the request.
607 :param dict params: URL query parameters.
608 :param dict headers: Headers
609 :param content: The body content
610 :type content: bytes or str or dict
611 :param dict form_content: Form content
612 :param stream_content: The body content as a stream
613 :type stream_content: stream or generator or asyncgenerator
614 :return: An HttpRequest object
615 :rtype: ~azure.core.pipeline.transport.HttpRequest
616 """
617 request = HttpRequest(method, self.format_url(url))
618
619 if params:
620 request.format_parameters(params)
621
622 if headers:
623 request.headers.update(headers)
624
625 if content is not None:
626 content_type = request.headers.get("Content-Type")
627 if isinstance(content, ET.Element):
628 request.set_xml_body(content)
629 # https://github.com/Azure/azure-sdk-for-python/issues/12137
630 # A string is valid JSON, make the difference between text
631 # and a plain JSON string.
632 # Content-Type is a good indicator of intent from user
633 elif content_type and content_type.startswith("text/"):
634 request.set_text_body(content)
635 else:
636 try:
637 request.set_json_body(content)
638 except TypeError:
639 request.data = content
640
641 if form_content:
642 request.set_formdata_body(form_content)
643 elif stream_content:
644 request.set_streamed_data_body(stream_content)
645
646 return request
647
648 def format_url(self, url_template: str, **kwargs: Any) -> str:
649 """Format request URL with the client base URL, unless the
650 supplied URL is already absolute.
651
652 Note that both the base url and the template url can contain query parameters.
653
654 :param str url_template: The request URL to be formatted if necessary.
655 :rtype: str
656 :return: The formatted URL.
657 """
658 url = _format_url_section(url_template, **kwargs)
659 if url:
660 parsed = urlparse(url)
661 if not parsed.scheme or not parsed.netloc:
662 url = url.lstrip("/")
663 try:
664 base = self._base_url.format(**kwargs).rstrip("/")
665 except KeyError as key:
666 err_msg = "The value provided for the url part {} was incorrect, and resulted in an invalid url"
667 raise ValueError(err_msg.format(key.args[0])) from key
668
669 url = _urljoin(base, url)
670 else:
671 url = self._base_url.format(**kwargs)
672 return url
673
674 def get(
675 self,
676 url: str,
677 params: Optional[Dict[str, str]] = None,
678 headers: Optional[Dict[str, str]] = None,
679 content: Any = None,
680 form_content: Optional[Dict[str, Any]] = None,
681 ) -> "HttpRequest":
682 """Create a GET request object.
683
684 :param str url: The request URL.
685 :param dict params: Request URL parameters.
686 :param dict headers: Headers
687 :param content: The body content
688 :type content: bytes or str or dict
689 :param dict form_content: Form content
690 :return: An HttpRequest object
691 :rtype: ~azure.core.pipeline.transport.HttpRequest
692 """
693 request = self._request("GET", url, params, headers, content, form_content, None)
694 request.method = "GET"
695 return request
696
697 def put(
698 self,
699 url: str,
700 params: Optional[Dict[str, str]] = None,
701 headers: Optional[Dict[str, str]] = None,
702 content: Any = None,
703 form_content: Optional[Dict[str, Any]] = None,
704 stream_content: Any = None,
705 ) -> HttpRequest:
706 """Create a PUT request object.
707
708 :param str url: The request URL.
709 :param dict params: Request URL parameters.
710 :param dict headers: Headers
711 :param content: The body content
712 :type content: bytes or str or dict
713 :param dict form_content: Form content
714 :param stream_content: The body content as a stream
715 :type stream_content: stream or generator or asyncgenerator
716 :return: An HttpRequest object
717 :rtype: ~azure.core.pipeline.transport.HttpRequest
718 """
719 request = self._request("PUT", url, params, headers, content, form_content, stream_content)
720 return request
721
722 def post(
723 self,
724 url: str,
725 params: Optional[Dict[str, str]] = None,
726 headers: Optional[Dict[str, str]] = None,
727 content: Any = None,
728 form_content: Optional[Dict[str, Any]] = None,
729 stream_content: Any = None,
730 ) -> HttpRequest:
731 """Create a POST request object.
732
733 :param str url: The request URL.
734 :param dict params: Request URL parameters.
735 :param dict headers: Headers
736 :param content: The body content
737 :type content: bytes or str or dict
738 :param dict form_content: Form content
739 :param stream_content: The body content as a stream
740 :type stream_content: stream or generator or asyncgenerator
741 :return: An HttpRequest object
742 :rtype: ~azure.core.pipeline.transport.HttpRequest
743 """
744 request = self._request("POST", url, params, headers, content, form_content, stream_content)
745 return request
746
747 def head(
748 self,
749 url: str,
750 params: Optional[Dict[str, str]] = None,
751 headers: Optional[Dict[str, str]] = None,
752 content: Any = None,
753 form_content: Optional[Dict[str, Any]] = None,
754 stream_content: Any = None,
755 ) -> HttpRequest:
756 """Create a HEAD request object.
757
758 :param str url: The request URL.
759 :param dict params: Request URL parameters.
760 :param dict headers: Headers
761 :param content: The body content
762 :type content: bytes or str or dict
763 :param dict form_content: Form content
764 :param stream_content: The body content as a stream
765 :type stream_content: stream or generator or asyncgenerator
766 :return: An HttpRequest object
767 :rtype: ~azure.core.pipeline.transport.HttpRequest
768 """
769 request = self._request("HEAD", url, params, headers, content, form_content, stream_content)
770 return request
771
772 def patch(
773 self,
774 url: str,
775 params: Optional[Dict[str, str]] = None,
776 headers: Optional[Dict[str, str]] = None,
777 content: Any = None,
778 form_content: Optional[Dict[str, Any]] = None,
779 stream_content: Any = None,
780 ) -> HttpRequest:
781 """Create a PATCH request object.
782
783 :param str url: The request URL.
784 :param dict params: Request URL parameters.
785 :param dict headers: Headers
786 :param content: The body content
787 :type content: bytes or str or dict
788 :param dict form_content: Form content
789 :param stream_content: The body content as a stream
790 :type stream_content: stream or generator or asyncgenerator
791 :return: An HttpRequest object
792 :rtype: ~azure.core.pipeline.transport.HttpRequest
793 """
794 request = self._request("PATCH", url, params, headers, content, form_content, stream_content)
795 return request
796
797 def delete(
798 self,
799 url: str,
800 params: Optional[Dict[str, str]] = None,
801 headers: Optional[Dict[str, str]] = None,
802 content: Any = None,
803 form_content: Optional[Dict[str, Any]] = None,
804 ) -> HttpRequest:
805 """Create a DELETE request object.
806
807 :param str url: The request URL.
808 :param dict params: Request URL parameters.
809 :param dict headers: Headers
810 :param content: The body content
811 :type content: bytes or str or dict
812 :param dict form_content: Form content
813 :return: An HttpRequest object
814 :rtype: ~azure.core.pipeline.transport.HttpRequest
815 """
816 request = self._request("DELETE", url, params, headers, content, form_content, None)
817 return request
818
819 def merge(
820 self,
821 url: str,
822 params: Optional[Dict[str, str]] = None,
823 headers: Optional[Dict[str, str]] = None,
824 content: Any = None,
825 form_content: Optional[Dict[str, Any]] = None,
826 ) -> HttpRequest:
827 """Create a MERGE request object.
828
829 :param str url: The request URL.
830 :param dict params: Request URL parameters.
831 :param dict headers: Headers
832 :param content: The body content
833 :type content: bytes or str or dict
834 :param dict form_content: Form content
835 :return: An HttpRequest object
836 :rtype: ~azure.core.pipeline.transport.HttpRequest
837 """
838 request = self._request("MERGE", url, params, headers, content, form_content, None)
839 return request
840
841 def options(
842 self, # pylint: disable=unused-argument
843 url: str,
844 params: Optional[Dict[str, str]] = None,
845 headers: Optional[Dict[str, str]] = None,
846 *,
847 content: Optional[Union[bytes, str, Dict[Any, Any]]] = None,
848 form_content: Optional[Dict[Any, Any]] = None,
849 **kwargs: Any,
850 ) -> HttpRequest:
851 """Create a OPTIONS request object.
852
853 :param str url: The request URL.
854 :param dict params: Request URL parameters.
855 :param dict headers: Headers
856 :keyword content: The body content
857 :type content: bytes or str or dict
858 :keyword dict form_content: Form content
859 :return: An HttpRequest object
860 :rtype: ~azure.core.pipeline.transport.HttpRequest
861 """
862 request = self._request("OPTIONS", url, params, headers, content, form_content, None)
863 return request