Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/urllib3/_request_methods.py: 34%
53 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +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`.
88 """
89 method = method.upper()
91 if json is not None and body is not None:
92 raise TypeError(
93 "request got values for both 'body' and 'json' parameters which are mutually exclusive"
94 )
96 if json is not None:
97 if headers is None:
98 headers = self.headers.copy() # type: ignore
99 if not ("content-type" in map(str.lower, headers.keys())):
100 headers["Content-Type"] = "application/json" # type: ignore
102 body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode(
103 "utf-8"
104 )
106 if body is not None:
107 urlopen_kw["body"] = body
109 if method in self._encode_url_methods:
110 return self.request_encode_url(
111 method,
112 url,
113 fields=fields, # type: ignore[arg-type]
114 headers=headers,
115 **urlopen_kw,
116 )
117 else:
118 return self.request_encode_body(
119 method, url, fields=fields, headers=headers, **urlopen_kw
120 )
122 def request_encode_url(
123 self,
124 method: str,
125 url: str,
126 fields: _TYPE_ENCODE_URL_FIELDS | None = None,
127 headers: typing.Mapping[str, str] | None = None,
128 **urlopen_kw: str,
129 ) -> BaseHTTPResponse:
130 """
131 Make a request using :meth:`urlopen` with the ``fields`` encoded in
132 the url. This is useful for request methods like GET, HEAD, DELETE, etc.
133 """
134 if headers is None:
135 headers = self.headers
137 extra_kw: dict[str, typing.Any] = {"headers": headers}
138 extra_kw.update(urlopen_kw)
140 if fields:
141 url += "?" + urlencode(fields)
143 return self.urlopen(method, url, **extra_kw)
145 def request_encode_body(
146 self,
147 method: str,
148 url: str,
149 fields: _TYPE_FIELDS | None = None,
150 headers: typing.Mapping[str, str] | None = None,
151 encode_multipart: bool = True,
152 multipart_boundary: str | None = None,
153 **urlopen_kw: str,
154 ) -> BaseHTTPResponse:
155 """
156 Make a request using :meth:`urlopen` with the ``fields`` encoded in
157 the body. This is useful for request methods like POST, PUT, PATCH, etc.
159 When ``encode_multipart=True`` (default), then
160 :func:`urllib3.encode_multipart_formdata` is used to encode
161 the payload with the appropriate content type. Otherwise
162 :func:`urllib.parse.urlencode` is used with the
163 'application/x-www-form-urlencoded' content type.
165 Multipart encoding must be used when posting files, and it's reasonably
166 safe to use it in other times too. However, it may break request
167 signing, such as with OAuth.
169 Supports an optional ``fields`` parameter of key/value strings AND
170 key/filetuple. A filetuple is a (filename, data, MIME type) tuple where
171 the MIME type is optional. For example::
173 fields = {
174 'foo': 'bar',
175 'fakefile': ('foofile.txt', 'contents of foofile'),
176 'realfile': ('barfile.txt', open('realfile').read()),
177 'typedfile': ('bazfile.bin', open('bazfile').read(),
178 'image/jpeg'),
179 'nonamefile': 'contents of nonamefile field',
180 }
182 When uploading a file, providing a filename (the first parameter of the
183 tuple) is optional but recommended to best mimic behavior of browsers.
185 Note that if ``headers`` are supplied, the 'Content-Type' header will
186 be overwritten because it depends on the dynamic random boundary string
187 which is used to compose the body of the request. The random boundary
188 string can be explicitly set with the ``multipart_boundary`` parameter.
189 """
190 if headers is None:
191 headers = self.headers
193 extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)}
194 body: bytes | str
196 if fields:
197 if "body" in urlopen_kw:
198 raise TypeError(
199 "request got values for both 'fields' and 'body', can only specify one."
200 )
202 if encode_multipart:
203 body, content_type = encode_multipart_formdata(
204 fields, boundary=multipart_boundary
205 )
206 else:
207 body, content_type = (
208 urlencode(fields), # type: ignore[arg-type]
209 "application/x-www-form-urlencoded",
210 )
212 extra_kw["body"] = body
213 extra_kw["headers"].setdefault("Content-Type", content_type)
215 extra_kw.update(urlopen_kw)
217 return self.urlopen(method, url, **extra_kw)