Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/urllib3/_request_methods.py: 33%
54 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1from __future__ import annotations
3import json as _json
4import typing
5from urllib.parse import urlencode
7from ._base_connection import _TYPE_BODY
8from ._collections import HTTPHeaderDict
9from .filepost import _TYPE_FIELDS, encode_multipart_formdata
10from .response import BaseHTTPResponse
12__all__ = ["RequestMethods"]
14_TYPE_ENCODE_URL_FIELDS = typing.Union[
15 typing.Sequence[typing.Tuple[str, typing.Union[str, bytes]]],
16 typing.Mapping[str, typing.Union[str, bytes]],
17]
20class RequestMethods:
21 """
22 Convenience mixin for classes who implement a :meth:`urlopen` method, such
23 as :class:`urllib3.HTTPConnectionPool` and
24 :class:`urllib3.PoolManager`.
26 Provides behavior for making common types of HTTP request methods and
27 decides which type of request field encoding to use.
29 Specifically,
31 :meth:`.request_encode_url` is for sending requests whose fields are
32 encoded in the URL (such as GET, HEAD, DELETE).
34 :meth:`.request_encode_body` is for sending requests whose fields are
35 encoded in the *body* of the request using multipart or www-form-urlencoded
36 (such as for POST, PUT, PATCH).
38 :meth:`.request` is for making any kind of request, it will look up the
39 appropriate encoding format and use one of the above two methods to make
40 the request.
42 Initializer parameters:
44 :param headers:
45 Headers to include with all requests, unless other headers are given
46 explicitly.
47 """
49 _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"}
51 def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None:
52 self.headers = headers or {}
54 def urlopen(
55 self,
56 method: str,
57 url: str,
58 body: _TYPE_BODY | None = None,
59 headers: typing.Mapping[str, str] | None = None,
60 encode_multipart: bool = True,
61 multipart_boundary: str | None = None,
62 **kw: typing.Any,
63 ) -> BaseHTTPResponse: # Abstract
64 raise NotImplementedError(
65 "Classes extending RequestMethods must implement "
66 "their own ``urlopen`` method."
67 )
69 def request(
70 self,
71 method: str,
72 url: str,
73 body: _TYPE_BODY | None = None,
74 fields: _TYPE_FIELDS | None = None,
75 headers: typing.Mapping[str, str] | None = None,
76 json: typing.Any | None = None,
77 **urlopen_kw: typing.Any,
78 ) -> BaseHTTPResponse:
79 """
80 Make a request using :meth:`urlopen` with the appropriate encoding of
81 ``fields`` based on the ``method`` used.
83 This is a convenience method that requires the least amount of manual
84 effort. It can be used in most situations, while still having the
85 option to drop down to more specific methods when necessary, such as
86 :meth:`request_encode_url`, :meth:`request_encode_body`,
87 or even the lowest level :meth:`urlopen`.
89 :param method:
90 HTTP request method (such as GET, POST, PUT, etc.)
92 :param url:
93 The URL to perform the request on.
95 :param body:
96 Data to send in the request body, either :class:`str`, :class:`bytes`,
97 an iterable of :class:`str`/:class:`bytes`, or a file-like object.
99 :param fields:
100 Data to encode and send in the request body. Values are processed
101 by :func:`urllib.parse.urlencode`.
103 :param headers:
104 Dictionary of custom headers to send, such as User-Agent,
105 If-None-Match, etc. If None, pool headers are used. If provided,
106 these headers completely replace any pool-specific headers.
108 :param json:
109 Data to encode and send as JSON with UTF-encoded in the request body.
110 The ``"Content-Type"`` header will be set to ``"application/json"``
111 unless specified otherwise.
112 """
113 method = method.upper()
115 if json is not None and body is not None:
116 raise TypeError(
117 "request got values for both 'body' and 'json' parameters which are mutually exclusive"
118 )
120 if json is not None:
121 if headers is None:
122 headers = self.headers
124 if not ("content-type" in map(str.lower, headers.keys())):
125 headers = HTTPHeaderDict(headers)
126 headers["Content-Type"] = "application/json"
128 body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode(
129 "utf-8"
130 )
132 if body is not None:
133 urlopen_kw["body"] = body
135 if method in self._encode_url_methods:
136 return self.request_encode_url(
137 method,
138 url,
139 fields=fields, # type: ignore[arg-type]
140 headers=headers,
141 **urlopen_kw,
142 )
143 else:
144 return self.request_encode_body(
145 method, url, fields=fields, headers=headers, **urlopen_kw
146 )
148 def request_encode_url(
149 self,
150 method: str,
151 url: str,
152 fields: _TYPE_ENCODE_URL_FIELDS | None = None,
153 headers: typing.Mapping[str, str] | None = None,
154 **urlopen_kw: str,
155 ) -> BaseHTTPResponse:
156 """
157 Make a request using :meth:`urlopen` with the ``fields`` encoded in
158 the url. This is useful for request methods like GET, HEAD, DELETE, etc.
160 :param method:
161 HTTP request method (such as GET, POST, PUT, etc.)
163 :param url:
164 The URL to perform the request on.
166 :param fields:
167 Data to encode and send in the request body.
169 :param headers:
170 Dictionary of custom headers to send, such as User-Agent,
171 If-None-Match, etc. If None, pool headers are used. If provided,
172 these headers completely replace any pool-specific headers.
173 """
174 if headers is None:
175 headers = self.headers
177 extra_kw: dict[str, typing.Any] = {"headers": headers}
178 extra_kw.update(urlopen_kw)
180 if fields:
181 url += "?" + urlencode(fields)
183 return self.urlopen(method, url, **extra_kw)
185 def request_encode_body(
186 self,
187 method: str,
188 url: str,
189 fields: _TYPE_FIELDS | None = None,
190 headers: typing.Mapping[str, str] | None = None,
191 encode_multipart: bool = True,
192 multipart_boundary: str | None = None,
193 **urlopen_kw: str,
194 ) -> BaseHTTPResponse:
195 """
196 Make a request using :meth:`urlopen` with the ``fields`` encoded in
197 the body. This is useful for request methods like POST, PUT, PATCH, etc.
199 When ``encode_multipart=True`` (default), then
200 :func:`urllib3.encode_multipart_formdata` is used to encode
201 the payload with the appropriate content type. Otherwise
202 :func:`urllib.parse.urlencode` is used with the
203 'application/x-www-form-urlencoded' content type.
205 Multipart encoding must be used when posting files, and it's reasonably
206 safe to use it in other times too. However, it may break request
207 signing, such as with OAuth.
209 Supports an optional ``fields`` parameter of key/value strings AND
210 key/filetuple. A filetuple is a (filename, data, MIME type) tuple where
211 the MIME type is optional. For example::
213 fields = {
214 'foo': 'bar',
215 'fakefile': ('foofile.txt', 'contents of foofile'),
216 'realfile': ('barfile.txt', open('realfile').read()),
217 'typedfile': ('bazfile.bin', open('bazfile').read(),
218 'image/jpeg'),
219 'nonamefile': 'contents of nonamefile field',
220 }
222 When uploading a file, providing a filename (the first parameter of the
223 tuple) is optional but recommended to best mimic behavior of browsers.
225 Note that if ``headers`` are supplied, the 'Content-Type' header will
226 be overwritten because it depends on the dynamic random boundary string
227 which is used to compose the body of the request. The random boundary
228 string can be explicitly set with the ``multipart_boundary`` parameter.
230 :param method:
231 HTTP request method (such as GET, POST, PUT, etc.)
233 :param url:
234 The URL to perform the request on.
236 :param fields:
237 Data to encode and send in the request body.
239 :param headers:
240 Dictionary of custom headers to send, such as User-Agent,
241 If-None-Match, etc. If None, pool headers are used. If provided,
242 these headers completely replace any pool-specific headers.
244 :param encode_multipart:
245 If True, encode the ``fields`` using the multipart/form-data MIME
246 format.
248 :param multipart_boundary:
249 If not specified, then a random boundary will be generated using
250 :func:`urllib3.filepost.choose_boundary`.
251 """
252 if headers is None:
253 headers = self.headers
255 extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)}
256 body: bytes | str
258 if fields:
259 if "body" in urlopen_kw:
260 raise TypeError(
261 "request got values for both 'fields' and 'body', can only specify one."
262 )
264 if encode_multipart:
265 body, content_type = encode_multipart_formdata(
266 fields, boundary=multipart_boundary
267 )
268 else:
269 body, content_type = (
270 urlencode(fields), # type: ignore[arg-type]
271 "application/x-www-form-urlencoded",
272 )
274 extra_kw["body"] = body
275 extra_kw["headers"].setdefault("Content-Type", content_type)
277 extra_kw.update(urlopen_kw)
279 return self.urlopen(method, url, **extra_kw)