-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_client.py
More file actions
181 lines (152 loc) · 5.22 KB
/
_client.py
File metadata and controls
181 lines (152 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import types
import typing
from ._content import Content, JSON, HTML, Text, Binary, select_media
from ._headers import Headers
from ._pool import ConnectionPool, Transport
from ._request import Method, Request
from ._response import Response
from ._urls import URL
__all__ = ["Client"]
class Client:
def __init__(
self,
url: URL | str | None = None,
headers: Headers | typing.Mapping[str, str] | None = None,
transport: Transport | None = None,
):
if url is None:
url = ""
if headers is None:
headers = {"User-Agent": "dev"}
if transport is None:
transport = ConnectionPool()
self.url = URL(url)
self.headers = Headers(headers)
self.transport = transport
self.via = RedirectMiddleware(self.transport)
self.media = {
'application/json': JSON,
'text/html': HTML,
'text/*': Text,
'*/*': Binary,
}
def build_request(
self,
method: Method | str,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
) -> Request:
return Request(
method=method,
url=self.url.join(url),
headers=self.headers.copy_update(headers),
content=content,
)
def request(
self,
method: Method | str,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
) -> Response:
request = self.build_request(method, url, headers=headers, content=content)
with self.via.send(request) as stream:
ct = stream.headers.get('Content-Type', 'application/octet-stream')
cls = select_media(self.media, ct)
content = cls.parse(stream)
return Response(
status_code=stream.status_code,
headers=stream.headers,
content=content,
)
def stream(
self,
method: Method | str,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
) -> Response:
request = self.build_request(method, url, headers=headers, content=content)
return self.via.send(request)
def get(
self,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
):
return self.request("GET", url, headers=headers)
def post(
self,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
):
return self.request("POST", url, headers=headers, content=content)
def put(
self,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
):
return self.request("PUT", url, headers=headers, content=content)
def patch(
self,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
content: Content | bytes | None = None,
):
return self.request("PATCH", url, headers=headers, content=content)
def delete(
self,
url: URL | str,
headers: Headers | dict[str, str] | None = None,
):
return self.request("DELETE", url, headers=headers)
def close(self):
self.transport.close()
def __enter__(self):
return self
def __exit__(
self,
exc_type: type[BaseException] | None = None,
exc_value: BaseException | None = None,
traceback: types.TracebackType | None = None
):
self.close()
def __repr__(self):
return f"<Client [{self.transport.description()}]>"
class RedirectMiddleware(Transport):
def __init__(self, transport: Transport) -> None:
self._transport = transport
def build_redirect_request(self, request: Request, response: Response) -> Request | None:
# Redirect status codes...
if response.status_code not in (301, 302, 303, 307, 308):
return None
# Redirects need a valid location header...
try:
location = URL(response.headers['Location'])
except (KeyError, ValueError):
return None
# Instantiate a redirect request...
method = request.method
url = request.url.join(location)
headers = request.headers
content = request.content
return Request(method, url, headers, content)
def send(self, request: Request) -> Response:
while True:
response = self._transport.send(request)
# Determine if we have a redirect or not.
redirect = self.build_redirect_request(request, response)
# If we don't have a redirect, we're done.
if redirect is None:
return response
# If we have a redirect, then we read the body of the response.
# Ensures that the HTTP connection is available for a new
# request/response cycle.
with response as stream:
stream.read()
# Make the next request
request = redirect
def close(self):
pass