omlish 0.0.0.dev71__py3-none-any.whl → 0.0.0.dev72__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/formats/json/cli.py +3 -0
- omlish/http/__init__.py +1 -1
- omlish/http/clients.py +239 -0
- omlish/http/consts.py +4 -0
- omlish/http/headers.py +42 -7
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/RECORD +12 -12
- omlish/http/client.py +0 -142
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev71.dist-info → omlish-0.0.0.dev72.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/formats/json/cli.py
CHANGED
@@ -13,6 +13,7 @@ from .render import JsonRenderer
|
|
13
13
|
|
14
14
|
|
15
15
|
if ta.TYPE_CHECKING:
|
16
|
+
import ast
|
16
17
|
import tomllib
|
17
18
|
|
18
19
|
import yaml
|
@@ -21,6 +22,7 @@ if ta.TYPE_CHECKING:
|
|
21
22
|
from .. import props
|
22
23
|
|
23
24
|
else:
|
25
|
+
ast = lang.proxy_import('ast')
|
24
26
|
tomllib = lang.proxy_import('tomllib')
|
25
27
|
|
26
28
|
yaml = lang.proxy_import('yaml')
|
@@ -50,6 +52,7 @@ class Formats(enum.Enum):
|
|
50
52
|
TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
|
51
53
|
ENV = Format(['env', 'dotenv'], lambda f: dotenv.dotenv_values(stream=f))
|
52
54
|
PROPS = Format(['properties', 'props'], lambda f: dict(props.Properties().load(f.read())))
|
55
|
+
PY = Format(['py', 'python', 'repr'], lambda f: ast.literal_eval(f.read()))
|
53
56
|
|
54
57
|
|
55
58
|
FORMATS_BY_NAME: ta.Mapping[str, Format] = {
|
omlish/http/__init__.py
CHANGED
omlish/http/clients.py
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- check=False
|
4
|
+
- return non-200 HttpResponses
|
5
|
+
- async
|
6
|
+
- stream
|
7
|
+
"""
|
8
|
+
import abc
|
9
|
+
import http.client
|
10
|
+
import typing as ta
|
11
|
+
import urllib.error
|
12
|
+
import urllib.request
|
13
|
+
|
14
|
+
from .. import cached
|
15
|
+
from .. import dataclasses as dc
|
16
|
+
from .. import lang
|
17
|
+
from .headers import CanHttpHeaders
|
18
|
+
from .headers import HttpHeaders
|
19
|
+
|
20
|
+
|
21
|
+
if ta.TYPE_CHECKING:
|
22
|
+
import httpx
|
23
|
+
else:
|
24
|
+
httpx = lang.proxy_import('httpx')
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
DEFAULT_ENCODING = 'utf-8'
|
31
|
+
|
32
|
+
|
33
|
+
def is_success_status(status: int) -> bool:
|
34
|
+
return 200 <= status < 300
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
@dc.dataclass(frozen=True)
|
41
|
+
class HttpRequest(lang.Final):
|
42
|
+
url: str
|
43
|
+
method: str | None = None # noqa
|
44
|
+
|
45
|
+
_: dc.KW_ONLY
|
46
|
+
|
47
|
+
headers: CanHttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
|
48
|
+
data: bytes | str | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
|
49
|
+
|
50
|
+
timeout_s: float | None = None
|
51
|
+
|
52
|
+
#
|
53
|
+
|
54
|
+
@property
|
55
|
+
def method_or_default(self) -> str:
|
56
|
+
if self.method is not None:
|
57
|
+
return self.method
|
58
|
+
if self.data is not None:
|
59
|
+
return 'POST'
|
60
|
+
return 'GET'
|
61
|
+
|
62
|
+
@cached.property
|
63
|
+
def headers_(self) -> HttpHeaders | None:
|
64
|
+
return HttpHeaders(self.headers) if self.headers is not None else None
|
65
|
+
|
66
|
+
|
67
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
68
|
+
class HttpResponse(lang.Final):
|
69
|
+
status: int
|
70
|
+
|
71
|
+
headers: HttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
|
72
|
+
data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
|
73
|
+
|
74
|
+
request: HttpRequest
|
75
|
+
underlying: ta.Any = dc.field(default=None, repr=False)
|
76
|
+
|
77
|
+
#
|
78
|
+
|
79
|
+
@property
|
80
|
+
def is_success(self) -> bool:
|
81
|
+
return is_success_status(self.status)
|
82
|
+
|
83
|
+
|
84
|
+
class HttpClientError(Exception):
|
85
|
+
@property
|
86
|
+
def cause(self) -> BaseException | None:
|
87
|
+
return self.__cause__
|
88
|
+
|
89
|
+
|
90
|
+
@dc.dataclass(frozen=True)
|
91
|
+
class HttpStatusError(HttpClientError):
|
92
|
+
response: HttpResponse
|
93
|
+
|
94
|
+
|
95
|
+
class HttpClient(lang.Abstract):
|
96
|
+
def __enter__(self) -> ta.Self:
|
97
|
+
return self
|
98
|
+
|
99
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
100
|
+
pass
|
101
|
+
|
102
|
+
def request(
|
103
|
+
self,
|
104
|
+
req: HttpRequest,
|
105
|
+
*,
|
106
|
+
check: bool = False,
|
107
|
+
) -> HttpResponse:
|
108
|
+
resp = self._request(req)
|
109
|
+
|
110
|
+
if check and not resp.is_success:
|
111
|
+
if isinstance(resp.underlying, Exception):
|
112
|
+
cause = resp.underlying
|
113
|
+
else:
|
114
|
+
cause = None
|
115
|
+
raise HttpStatusError(resp) from cause
|
116
|
+
|
117
|
+
return resp
|
118
|
+
|
119
|
+
@abc.abstractmethod
|
120
|
+
def _request(self, req: HttpRequest) -> HttpResponse:
|
121
|
+
raise NotImplementedError
|
122
|
+
|
123
|
+
|
124
|
+
##
|
125
|
+
|
126
|
+
|
127
|
+
class UrllibHttpClient(HttpClient):
|
128
|
+
def _request(self, req: HttpRequest) -> HttpResponse:
|
129
|
+
d: ta.Any
|
130
|
+
if (d := req.data) is not None:
|
131
|
+
if isinstance(d, str):
|
132
|
+
d = d.encode(DEFAULT_ENCODING)
|
133
|
+
|
134
|
+
# urllib headers are dumb dicts [1], and keys *must* be strings or it will automatically add problematic default
|
135
|
+
# headers because it doesn't see string keys in its header dict [2]. frustratingly it has no problem accepting
|
136
|
+
# bytes keys though [3].
|
137
|
+
# [1]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/urllib/request.py#L319-L325 # noqa
|
138
|
+
# [2]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/urllib/request.py#L1276-L1279 # noqa
|
139
|
+
# [3]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/http/client.py#L1300-L1301 # noqa
|
140
|
+
h: dict[str, str] = {}
|
141
|
+
if hs := req.headers_:
|
142
|
+
for k, v in hs.strict_dct.items():
|
143
|
+
h[k.decode('ascii')] = v.decode('ascii')
|
144
|
+
|
145
|
+
try:
|
146
|
+
with urllib.request.urlopen( # noqa
|
147
|
+
urllib.request.Request( # noqa
|
148
|
+
req.url,
|
149
|
+
method=req.method_or_default,
|
150
|
+
headers=h,
|
151
|
+
data=d,
|
152
|
+
),
|
153
|
+
timeout=req.timeout_s,
|
154
|
+
) as resp:
|
155
|
+
return HttpResponse(
|
156
|
+
status=resp.status,
|
157
|
+
headers=HttpHeaders(resp.headers.items()),
|
158
|
+
data=resp.read(),
|
159
|
+
request=req,
|
160
|
+
underlying=resp,
|
161
|
+
)
|
162
|
+
|
163
|
+
except urllib.error.HTTPError as e:
|
164
|
+
return HttpResponse(
|
165
|
+
status=e.code,
|
166
|
+
headers=HttpHeaders(e.headers.items()),
|
167
|
+
data=e.read(),
|
168
|
+
request=req,
|
169
|
+
underlying=e,
|
170
|
+
)
|
171
|
+
|
172
|
+
except (urllib.error.URLError, http.client.HTTPException) as e:
|
173
|
+
raise HttpClientError from e
|
174
|
+
|
175
|
+
|
176
|
+
##
|
177
|
+
|
178
|
+
|
179
|
+
class HttpxHttpClient(HttpClient):
|
180
|
+
def _request(self, req: HttpRequest) -> HttpResponse:
|
181
|
+
try:
|
182
|
+
response = httpx.request(
|
183
|
+
method=req.method_or_default,
|
184
|
+
url=req.url,
|
185
|
+
headers=req.headers_ or None, # type: ignore
|
186
|
+
content=req.data,
|
187
|
+
timeout=req.timeout_s,
|
188
|
+
)
|
189
|
+
|
190
|
+
return HttpResponse(
|
191
|
+
status=response.status_code,
|
192
|
+
headers=HttpHeaders(response.headers.raw),
|
193
|
+
data=response.content,
|
194
|
+
request=req,
|
195
|
+
underlying=response,
|
196
|
+
)
|
197
|
+
|
198
|
+
except httpx.HTTPError as e:
|
199
|
+
raise HttpClientError from e
|
200
|
+
|
201
|
+
|
202
|
+
##
|
203
|
+
|
204
|
+
|
205
|
+
def client() -> HttpClient:
|
206
|
+
return UrllibHttpClient()
|
207
|
+
|
208
|
+
|
209
|
+
def request(
|
210
|
+
url: str,
|
211
|
+
method: str | None = None,
|
212
|
+
*,
|
213
|
+
headers: CanHttpHeaders | None = None,
|
214
|
+
data: bytes | str | None = None,
|
215
|
+
|
216
|
+
timeout_s: float | None = None,
|
217
|
+
|
218
|
+
check: bool = False,
|
219
|
+
|
220
|
+
**kwargs: ta.Any,
|
221
|
+
) -> HttpResponse:
|
222
|
+
req = HttpRequest(
|
223
|
+
url,
|
224
|
+
method=method,
|
225
|
+
|
226
|
+
headers=headers,
|
227
|
+
data=data,
|
228
|
+
|
229
|
+
timeout_s=timeout_s,
|
230
|
+
|
231
|
+
**kwargs,
|
232
|
+
)
|
233
|
+
|
234
|
+
with client() as cli:
|
235
|
+
return cli.request(
|
236
|
+
req,
|
237
|
+
|
238
|
+
check=check,
|
239
|
+
)
|
omlish/http/consts.py
CHANGED
@@ -64,5 +64,9 @@ BEARER_AUTH_HEADER_PREFIX = b'Bearer '
|
|
64
64
|
BASIC_AUTH_HEADER_PREFIX = b'Basic '
|
65
65
|
|
66
66
|
|
67
|
+
def format_bearer_auth_header(token: str | bytes) -> bytes:
|
68
|
+
return BEARER_AUTH_HEADER_PREFIX + (token.encode('ascii') if isinstance(token, str) else token)
|
69
|
+
|
70
|
+
|
67
71
|
def format_basic_auth_header(username: str, password: str) -> bytes:
|
68
72
|
return BASIC_AUTH_HEADER_PREFIX + base64.b64encode(':'.join([username, password]).encode())
|
omlish/http/headers.py
CHANGED
@@ -9,9 +9,20 @@ StrOrBytes: ta.TypeAlias = str | bytes
|
|
9
9
|
|
10
10
|
CanHttpHeaders: ta.TypeAlias = ta.Union[
|
11
11
|
'HttpHeaders',
|
12
|
+
|
13
|
+
ta.Mapping[str, str],
|
14
|
+
ta.Mapping[str, ta.Sequence[str]],
|
15
|
+
|
16
|
+
ta.Mapping[bytes, bytes],
|
17
|
+
ta.Mapping[bytes, ta.Sequence[bytes]],
|
18
|
+
|
12
19
|
ta.Mapping[StrOrBytes, StrOrBytes],
|
13
20
|
ta.Mapping[StrOrBytes, ta.Sequence[StrOrBytes]],
|
21
|
+
|
14
22
|
ta.Mapping[StrOrBytes, StrOrBytes | ta.Sequence[StrOrBytes]],
|
23
|
+
|
24
|
+
ta.Sequence[tuple[str, str]],
|
25
|
+
ta.Sequence[tuple[bytes, bytes]],
|
15
26
|
ta.Sequence[tuple[StrOrBytes, StrOrBytes]],
|
16
27
|
]
|
17
28
|
|
@@ -38,7 +49,11 @@ class HttpHeaders:
|
|
38
49
|
raise TypeError(src)
|
39
50
|
|
40
51
|
elif isinstance(src, ta.Sequence):
|
41
|
-
for
|
52
|
+
for t in src:
|
53
|
+
if isinstance(t, (str, bytes)):
|
54
|
+
raise TypeError(t)
|
55
|
+
|
56
|
+
k, v = t
|
42
57
|
lst.append((self._as_bytes(k), self._as_bytes(v)))
|
43
58
|
|
44
59
|
else:
|
@@ -74,9 +89,23 @@ class HttpHeaders:
|
|
74
89
|
|
75
90
|
#
|
76
91
|
|
92
|
+
@property
|
93
|
+
def raw(self) -> ta.Sequence[tuple[bytes, bytes]]:
|
94
|
+
return self._lst
|
95
|
+
|
96
|
+
@classmethod
|
97
|
+
def _as_key(cls, o: StrOrBytes) -> bytes:
|
98
|
+
return cls._as_bytes(o).lower()
|
99
|
+
|
100
|
+
@cached.property
|
101
|
+
def normalized(self) -> ta.Sequence[tuple[bytes, bytes]]:
|
102
|
+
return [(self._as_key(k), v) for k, v in self._lst]
|
103
|
+
|
104
|
+
#
|
105
|
+
|
77
106
|
@cached.property
|
78
107
|
def multi_dct(self) -> ta.Mapping[bytes, ta.Sequence[bytes]]:
|
79
|
-
return col.multi_map(self.
|
108
|
+
return col.multi_map(self.normalized)
|
80
109
|
|
81
110
|
@cached.property
|
82
111
|
def single_dct(self) -> ta.Mapping[bytes, bytes]:
|
@@ -84,13 +113,13 @@ class HttpHeaders:
|
|
84
113
|
|
85
114
|
@cached.property
|
86
115
|
def strict_dct(self) -> ta.Mapping[bytes, bytes]:
|
87
|
-
return col.make_map(self.
|
116
|
+
return col.make_map(self.normalized, strict=True)
|
88
117
|
|
89
118
|
#
|
90
119
|
|
91
120
|
@cached.property
|
92
121
|
def strs(self) -> ta.Sequence[tuple[str, str]]:
|
93
|
-
return tuple((k.decode(self.ENCODING), v.decode(self.ENCODING)) for k, v in self.
|
122
|
+
return tuple((k.decode(self.ENCODING), v.decode(self.ENCODING)) for k, v in self.normalized)
|
94
123
|
|
95
124
|
@cached.property
|
96
125
|
def multi_str_dct(self) -> ta.Mapping[str, ta.Sequence[str]]:
|
@@ -116,7 +145,11 @@ class HttpHeaders:
|
|
116
145
|
return iter(self._lst)
|
117
146
|
|
118
147
|
@ta.overload
|
119
|
-
def __getitem__(self, item:
|
148
|
+
def __getitem__(self, item: str) -> ta.Sequence[str]:
|
149
|
+
...
|
150
|
+
|
151
|
+
@ta.overload
|
152
|
+
def __getitem__(self, item: bytes) -> ta.Sequence[bytes]:
|
120
153
|
...
|
121
154
|
|
122
155
|
@ta.overload
|
@@ -130,8 +163,10 @@ class HttpHeaders:
|
|
130
163
|
def __getitem__(self, item):
|
131
164
|
if isinstance(item, (int, slice)):
|
132
165
|
return self._lst[item]
|
133
|
-
elif isinstance(item,
|
134
|
-
return self.
|
166
|
+
elif isinstance(item, str):
|
167
|
+
return self.multi_str_dct[item.lower()]
|
168
|
+
elif isinstance(item, bytes):
|
169
|
+
return self.multi_dct[self._as_key(item)]
|
135
170
|
else:
|
136
171
|
raise TypeError(item)
|
137
172
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=TXvFdkAU0Zr2FKdo7fyvt9nr3UjCtrnAZ0diZXSAteE,1430
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=Xtux9lxQlFpaTqHV9Xq7BumGnu8QP3C6l4R7wQX9F_w,3420
|
3
3
|
omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
|
5
5
|
omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
|
@@ -184,7 +184,7 @@ omlish/formats/props.py,sha256=JwFJbKblqzqnzXf7YKFzQSDfcAXzkKsfoYvad6FPy98,18945
|
|
184
184
|
omlish/formats/yaml.py,sha256=DSJXUq9yanfxdS6ufNTyBHMtIZO57LRnJj4w9fLY1aM,6852
|
185
185
|
omlish/formats/json/__init__.py,sha256=moSR67Qkju2eYb_qVDtaivepe44mxAnYuC8OCSbtETg,298
|
186
186
|
omlish/formats/json/__main__.py,sha256=1wxxKZVkj_u7HCcewwMIbGuZj_Wph95yrUbm474Op9M,188
|
187
|
-
omlish/formats/json/cli.py,sha256=
|
187
|
+
omlish/formats/json/cli.py,sha256=pHFvYji6h_kMUyTgHCuDFofeDVY_5Em0wBqqVOJzDmI,3504
|
188
188
|
omlish/formats/json/json.py,sha256=y8d8WWgzGZDTjzYc_xe9v4T0foXHI-UP7gjCwnHzUIA,828
|
189
189
|
omlish/formats/json/render.py,sha256=6edhSrxXWW3nzRfokp5qaldT0_YAj-HVEan_rErf-vo,3208
|
190
190
|
omlish/formats/json/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -200,15 +200,15 @@ omlish/graphs/dot/items.py,sha256=OWPf0-hjBgS1uyy2QgAEn4IgFHJcEg7sHVWeTx1ghZc,40
|
|
200
200
|
omlish/graphs/dot/make.py,sha256=RN30gHfJPiXx5Q51kbDdhVJYf59Fr84Lz9J-mXRt9sI,360
|
201
201
|
omlish/graphs/dot/rendering.py,sha256=2UgXvMRN4Z9cfIqLlC7Iu_8bWbwUDEL4opHHkFfSqTw,3630
|
202
202
|
omlish/graphs/dot/utils.py,sha256=_FMwn77WfiiAfLsRTOKWm4IYbNv5kQN22YJ5psw6CWg,801
|
203
|
-
omlish/http/__init__.py,sha256
|
203
|
+
omlish/http/__init__.py,sha256=-ENDALr8ehHvivRD6cxIbEC94t0RHhrakf6CQRDTc8o,625
|
204
204
|
omlish/http/asgi.py,sha256=wXhBZ21bEl32Kv9yBrRwUR_7pHEgVtHP8ZZwbasQ6-4,3307
|
205
|
-
omlish/http/
|
205
|
+
omlish/http/clients.py,sha256=KPWDw251eBmiYSXw7KafMP9UQHEiLiLdh9ZLPV7jydc,6002
|
206
206
|
omlish/http/collections.py,sha256=s8w5s4Gewgxxhe2Ai0R45PgJYYifrLgTbU3VXVflHj4,260
|
207
|
-
omlish/http/consts.py,sha256
|
207
|
+
omlish/http/consts.py,sha256=FTolezLknKU6WJjk_x2T3a5LEMlnZSqv7gzTq55lxcU,2147
|
208
208
|
omlish/http/cookies.py,sha256=uuOYlHR6e2SC3GM41V0aozK10nef9tYg83Scqpn5-HM,6351
|
209
209
|
omlish/http/dates.py,sha256=Otgp8wRxPgNGyzx8LFowu1vC4EKJYARCiAwLFncpfHM,2875
|
210
210
|
omlish/http/encodings.py,sha256=w2WoKajpaZnQH8j-IBvk5ZFL2O2pAU_iBvZnkocaTlw,164
|
211
|
-
omlish/http/headers.py,sha256=
|
211
|
+
omlish/http/headers.py,sha256=MO5uDwbViY6Z371Hl5OwTvh2DGl76_PnWHlurNwIsOs,4895
|
212
212
|
omlish/http/json.py,sha256=9XwAsl4966Mxrv-1ytyCqhcE6lbBJw-0_tFZzGszgHE,7440
|
213
213
|
omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
|
214
214
|
omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
|
@@ -433,9 +433,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
|
433
433
|
omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
|
434
434
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
435
435
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
436
|
-
omlish-0.0.0.
|
437
|
-
omlish-0.0.0.
|
438
|
-
omlish-0.0.0.
|
439
|
-
omlish-0.0.0.
|
440
|
-
omlish-0.0.0.
|
441
|
-
omlish-0.0.0.
|
436
|
+
omlish-0.0.0.dev72.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
437
|
+
omlish-0.0.0.dev72.dist-info/METADATA,sha256=fC-RJQd1zeLsNghRMhMtxbjiAbWfSE6bbIBTkW85Rxg,4167
|
438
|
+
omlish-0.0.0.dev72.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
439
|
+
omlish-0.0.0.dev72.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
440
|
+
omlish-0.0.0.dev72.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
441
|
+
omlish-0.0.0.dev72.dist-info/RECORD,,
|
omlish/http/client.py
DELETED
@@ -1,142 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TODO:
|
3
|
-
- return non-200 HttpResponses
|
4
|
-
- async
|
5
|
-
- stream
|
6
|
-
"""
|
7
|
-
import abc
|
8
|
-
import http.client
|
9
|
-
import typing as ta
|
10
|
-
import urllib.error
|
11
|
-
import urllib.request
|
12
|
-
|
13
|
-
from .. import cached
|
14
|
-
from .. import dataclasses as dc
|
15
|
-
from .. import lang
|
16
|
-
from .headers import CanHttpHeaders
|
17
|
-
from .headers import HttpHeaders
|
18
|
-
|
19
|
-
|
20
|
-
if ta.TYPE_CHECKING:
|
21
|
-
import httpx
|
22
|
-
else:
|
23
|
-
httpx = lang.proxy_import('httpx')
|
24
|
-
|
25
|
-
|
26
|
-
@dc.dataclass(frozen=True)
|
27
|
-
class HttpRequest(lang.Final):
|
28
|
-
url: str
|
29
|
-
method: str = 'GET' # noqa
|
30
|
-
|
31
|
-
_: dc.KW_ONLY
|
32
|
-
|
33
|
-
headers: CanHttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
|
34
|
-
data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
|
35
|
-
|
36
|
-
timeout_s: float | None = None
|
37
|
-
|
38
|
-
@cached.property
|
39
|
-
def headers_(self) -> HttpHeaders | None:
|
40
|
-
return HttpHeaders(self.headers) if self.headers is not None else None
|
41
|
-
|
42
|
-
|
43
|
-
@dc.dataclass(frozen=True, kw_only=True)
|
44
|
-
class HttpResponse(lang.Final):
|
45
|
-
code: int
|
46
|
-
|
47
|
-
headers: HttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
|
48
|
-
data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
|
49
|
-
|
50
|
-
request: HttpRequest
|
51
|
-
underlying: ta.Any = dc.field(default=None, repr=False)
|
52
|
-
|
53
|
-
|
54
|
-
class HttpClientError(Exception):
|
55
|
-
pass
|
56
|
-
|
57
|
-
|
58
|
-
class HttpClient(lang.Abstract):
|
59
|
-
def __enter__(self) -> ta.Self:
|
60
|
-
return self
|
61
|
-
|
62
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
63
|
-
pass
|
64
|
-
|
65
|
-
@abc.abstractmethod
|
66
|
-
def request(self, req: HttpRequest) -> HttpResponse:
|
67
|
-
raise NotImplementedError
|
68
|
-
|
69
|
-
|
70
|
-
class UrllibHttpClient(HttpClient):
|
71
|
-
def request(self, req: HttpRequest) -> HttpResponse:
|
72
|
-
try:
|
73
|
-
with urllib.request.urlopen( # noqa
|
74
|
-
urllib.request.Request( # noqa
|
75
|
-
req.url,
|
76
|
-
method=req.method,
|
77
|
-
headers=req.headers_ or {}, # type: ignore
|
78
|
-
data=req.data,
|
79
|
-
),
|
80
|
-
timeout=req.timeout_s,
|
81
|
-
) as resp:
|
82
|
-
return HttpResponse(
|
83
|
-
code=resp.status,
|
84
|
-
headers=HttpHeaders(resp.headers.items()),
|
85
|
-
data=resp.read(),
|
86
|
-
request=req,
|
87
|
-
underlying=resp,
|
88
|
-
)
|
89
|
-
except (urllib.error.URLError, http.client.HTTPException) as e:
|
90
|
-
raise HttpClientError from e
|
91
|
-
|
92
|
-
|
93
|
-
class HttpxHttpClient(HttpClient):
|
94
|
-
def request(self, req: HttpRequest) -> HttpResponse:
|
95
|
-
try:
|
96
|
-
response = httpx.request(
|
97
|
-
method=req.method,
|
98
|
-
url=req.url,
|
99
|
-
headers=req.headers_ or None, # type: ignore
|
100
|
-
content=req.data,
|
101
|
-
timeout=req.timeout_s,
|
102
|
-
)
|
103
|
-
return HttpResponse(
|
104
|
-
code=response.status_code,
|
105
|
-
headers=HttpHeaders(response.headers.raw),
|
106
|
-
data=response.content,
|
107
|
-
request=req,
|
108
|
-
underlying=response,
|
109
|
-
)
|
110
|
-
except httpx.HTTPError as e:
|
111
|
-
raise HttpClientError from e
|
112
|
-
|
113
|
-
|
114
|
-
def client() -> HttpClient:
|
115
|
-
return UrllibHttpClient()
|
116
|
-
|
117
|
-
|
118
|
-
def request(
|
119
|
-
url: str,
|
120
|
-
method: str = 'GET',
|
121
|
-
*,
|
122
|
-
headers: CanHttpHeaders | None = None,
|
123
|
-
data: bytes | None = None,
|
124
|
-
|
125
|
-
timeout_s: float | None = None,
|
126
|
-
|
127
|
-
**kwargs: ta.Any,
|
128
|
-
) -> HttpResponse:
|
129
|
-
req = HttpRequest(
|
130
|
-
url,
|
131
|
-
method=method,
|
132
|
-
|
133
|
-
headers=headers,
|
134
|
-
data=data,
|
135
|
-
|
136
|
-
timeout_s=timeout_s,
|
137
|
-
|
138
|
-
**kwargs,
|
139
|
-
)
|
140
|
-
|
141
|
-
with client() as cli:
|
142
|
-
return cli.request(req)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|