python-http_request 0.0.8__tar.gz → 0.0.9__tar.gz
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.
- {python_http_request-0.0.8 → python_http_request-0.0.9}/PKG-INFO +6 -4
- {python_http_request-0.0.8 → python_http_request-0.0.9}/http_request/__init__.py +171 -54
- {python_http_request-0.0.8 → python_http_request-0.0.9}/pyproject.toml +6 -4
- {python_http_request-0.0.8 → python_http_request-0.0.9}/LICENSE +0 -0
- {python_http_request-0.0.8 → python_http_request-0.0.9}/http_request/py.typed +0 -0
- {python_http_request-0.0.8 → python_http_request-0.0.9}/readme.md +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-http_request
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Python http response utils.
|
|
5
|
-
Home-page: https://github.com/ChenyangGao/
|
|
5
|
+
Home-page: https://github.com/ChenyangGao/python-modules/tree/main/python-http_request
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: http,request
|
|
8
8
|
Author: ChenyangGao
|
|
@@ -20,12 +20,14 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
20
20
|
Classifier: Topic :: Software Development
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: http_response (>=0.0.4)
|
|
24
|
+
Requires-Dist: orjson
|
|
24
25
|
Requires-Dist: python-asynctools (>=0.1.3)
|
|
25
26
|
Requires-Dist: python-dicttools (>=0.0.1)
|
|
27
|
+
Requires-Dist: python-ensure (>=0.0.1)
|
|
26
28
|
Requires-Dist: python-filewrap (>=0.2.8)
|
|
27
29
|
Requires-Dist: python-texttools (>=0.0.4)
|
|
28
|
-
Project-URL: Repository, https://github.com/ChenyangGao/
|
|
30
|
+
Project-URL: Repository, https://github.com/ChenyangGao/python-modules/tree/main/python-http_request
|
|
29
31
|
Description-Content-Type: text/markdown
|
|
30
32
|
|
|
31
33
|
# Python http response utils.
|
|
@@ -2,41 +2,58 @@
|
|
|
2
2
|
# encoding: utf-8
|
|
3
3
|
|
|
4
4
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
5
|
-
__version__ = (0, 0,
|
|
5
|
+
__version__ = (0, 0, 9)
|
|
6
6
|
__all__ = [
|
|
7
|
-
"SupportsGeturl", "url_origin", "complete_url", "
|
|
8
|
-
"
|
|
9
|
-
"encode_multipart_data", "encode_multipart_data_async",
|
|
7
|
+
"SupportsGeturl", "url_origin", "complete_url", "ensure_ascii_url",
|
|
8
|
+
"urlencode", "cookies_str_to_dict", "headers_str_to_dict_by_lines",
|
|
9
|
+
"headers_str_to_dict", "encode_multipart_data", "encode_multipart_data_async",
|
|
10
|
+
"normalize_request_args",
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
from collections import UserString
|
|
13
14
|
from collections.abc import (
|
|
14
|
-
AsyncIterable, AsyncIterator, Buffer, Iterable, Iterator,
|
|
15
|
+
AsyncIterable, AsyncIterator, Buffer, Iterable, Iterator,
|
|
16
|
+
Mapping, Sequence,
|
|
15
17
|
)
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from fractions import Fraction
|
|
16
20
|
from io import TextIOWrapper
|
|
17
21
|
from itertools import batched
|
|
18
22
|
from mimetypes import guess_type
|
|
23
|
+
from numbers import Integral, Real
|
|
19
24
|
from os import PathLike
|
|
20
25
|
from os.path import basename
|
|
21
26
|
from re import compile as re_compile, Pattern
|
|
22
|
-
from
|
|
23
|
-
from
|
|
27
|
+
from string import punctuation
|
|
28
|
+
from typing import runtime_checkable, Any, Final, Protocol, TypedDict
|
|
29
|
+
from urllib.parse import quote, urlparse, urlunparse
|
|
24
30
|
from uuid import uuid4
|
|
31
|
+
from yarl import URL
|
|
25
32
|
|
|
26
33
|
from asynctools import async_map
|
|
27
|
-
from dicttools import iter_items
|
|
34
|
+
from dicttools import dict_map, iter_items
|
|
35
|
+
from ensure import ensure_bytes, ensure_buffer, ensure_str
|
|
28
36
|
from filewrap import bio_chunk_iter, bio_chunk_async_iter, SupportsRead
|
|
29
|
-
from
|
|
37
|
+
from http_response import get_charset, get_mimetype
|
|
38
|
+
from orjson import dumps as json_dumps
|
|
30
39
|
from texttools import text_to_dict
|
|
31
40
|
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
type string = Buffer | str | UserString
|
|
34
43
|
|
|
44
|
+
QUERY_KEY_TRANSTAB: Final = {k: f"%{k:02X}" for k in b"&="}
|
|
35
45
|
CRE_URL_SCHEME_match: Final = re_compile(r"(?i:[a-z][a-z0-9.+-]*)://").match
|
|
36
46
|
|
|
37
47
|
|
|
48
|
+
class RequestArgs(TypedDict):
|
|
49
|
+
method: str
|
|
50
|
+
url: str
|
|
51
|
+
data: Buffer | Iterable[Buffer] | AsyncIterable[Buffer]
|
|
52
|
+
headers: dict[str, str]
|
|
53
|
+
|
|
54
|
+
|
|
38
55
|
@runtime_checkable
|
|
39
|
-
class SupportsGeturl
|
|
56
|
+
class SupportsGeturl[AnyStr: (bytes, str)](Protocol):
|
|
40
57
|
def geturl(self) -> AnyStr: ...
|
|
41
58
|
|
|
42
59
|
|
|
@@ -49,7 +66,7 @@ def url_origin(url: str, /, default_port: int = 0) -> str:
|
|
|
49
66
|
url = "http" + url
|
|
50
67
|
elif not CRE_URL_SCHEME_match(url):
|
|
51
68
|
url = "http://" + url
|
|
52
|
-
urlp =
|
|
69
|
+
urlp = urlparse(url)
|
|
53
70
|
scheme, netloc = urlp.scheme or "http", urlp.netloc or "localhost"
|
|
54
71
|
if default_port and not urlp.port:
|
|
55
72
|
netloc = netloc.removesuffix(":") + f":{default_port}"
|
|
@@ -65,7 +82,7 @@ def complete_url(url: str, /, default_port: int = 0) -> str:
|
|
|
65
82
|
url = "http" + url
|
|
66
83
|
elif not CRE_URL_SCHEME_match(url):
|
|
67
84
|
url = "http://" + url
|
|
68
|
-
urlp =
|
|
85
|
+
urlp = urlparse(url)
|
|
69
86
|
repl = {"query": "", "fragment": ""}
|
|
70
87
|
if not urlp.scheme:
|
|
71
88
|
repl["scheme"] = "http"
|
|
@@ -75,7 +92,53 @@ def complete_url(url: str, /, default_port: int = 0) -> str:
|
|
|
75
92
|
if default_port and not urlp.port:
|
|
76
93
|
netloc = netloc.removesuffix(":") + f":{default_port}"
|
|
77
94
|
repl["netloc"] = netloc
|
|
78
|
-
return
|
|
95
|
+
return urlunparse(urlp._replace(**repl)).rstrip("/")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def ensure_ascii_url(url: str, /) -> str:
|
|
99
|
+
if url.isascii():
|
|
100
|
+
return url
|
|
101
|
+
return quote(url, safe=punctuation)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def urlencode(
|
|
105
|
+
payload: string | Mapping[Any, Any] | Iterable[tuple[Any, Any]],
|
|
106
|
+
/,
|
|
107
|
+
encoding: str = "utf-8",
|
|
108
|
+
errors: str = "strict",
|
|
109
|
+
) -> str:
|
|
110
|
+
if isinstance(payload, str):
|
|
111
|
+
return payload
|
|
112
|
+
elif isinstance(payload, UserString):
|
|
113
|
+
return str(payload)
|
|
114
|
+
elif isinstance(payload, Buffer):
|
|
115
|
+
return str(payload, encoding, errors)
|
|
116
|
+
def encode_iter(payload: Iterable[tuple[Any, Any]], /) -> Iterator[str]:
|
|
117
|
+
for i, (k, v) in enumerate(payload):
|
|
118
|
+
if i:
|
|
119
|
+
yield "&"
|
|
120
|
+
if isinstance(k, Buffer):
|
|
121
|
+
k = str(k, encoding, errors)
|
|
122
|
+
else:
|
|
123
|
+
k = str(k)
|
|
124
|
+
yield k.translate(QUERY_KEY_TRANSTAB)
|
|
125
|
+
yield "="
|
|
126
|
+
if v is True:
|
|
127
|
+
yield "true"
|
|
128
|
+
elif v is False:
|
|
129
|
+
yield "false"
|
|
130
|
+
elif v is None:
|
|
131
|
+
yield "null"
|
|
132
|
+
elif isinstance(v, (str, UserString)):
|
|
133
|
+
pass
|
|
134
|
+
elif isinstance(v, Buffer):
|
|
135
|
+
v = str(v, encoding, errors)
|
|
136
|
+
elif isinstance(v, (Mapping, Iterable)):
|
|
137
|
+
v = json_dumps(v, default=json_default).decode("utf-8")
|
|
138
|
+
else:
|
|
139
|
+
v = str(v)
|
|
140
|
+
yield v.replace("&", "%26")
|
|
141
|
+
return "".join(encode_iter(iter_items(payload)))
|
|
79
142
|
|
|
80
143
|
|
|
81
144
|
def cookies_str_to_dict(
|
|
@@ -103,46 +166,6 @@ def headers_str_to_dict_by_lines(headers: str, /, ) -> dict[str, str]:
|
|
|
103
166
|
return dict(batched(lines, 2)) # type: ignore
|
|
104
167
|
|
|
105
168
|
|
|
106
|
-
def ensure_bytes(
|
|
107
|
-
o,
|
|
108
|
-
/,
|
|
109
|
-
encoding: str = "utf-8",
|
|
110
|
-
errors: str = "strict",
|
|
111
|
-
) -> bytes:
|
|
112
|
-
if isinstance(o, bytes):
|
|
113
|
-
return o
|
|
114
|
-
elif isinstance(o, memoryview):
|
|
115
|
-
return o.tobytes()
|
|
116
|
-
elif isinstance(o, Buffer):
|
|
117
|
-
return bytes(o)
|
|
118
|
-
elif isinstance(o, int):
|
|
119
|
-
return int_to_bytes(o)
|
|
120
|
-
elif isinstance(o, (str, UserString)):
|
|
121
|
-
return o.encode(encoding, errors)
|
|
122
|
-
try:
|
|
123
|
-
return bytes(o)
|
|
124
|
-
except TypeError:
|
|
125
|
-
return bytes(str(o), encoding, errors)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def ensure_buffer(
|
|
129
|
-
o,
|
|
130
|
-
/,
|
|
131
|
-
encoding: str = "utf-8",
|
|
132
|
-
errors: str = "strict",
|
|
133
|
-
) -> Buffer:
|
|
134
|
-
if isinstance(o, Buffer):
|
|
135
|
-
return o
|
|
136
|
-
elif isinstance(o, int):
|
|
137
|
-
return int_to_bytes(o)
|
|
138
|
-
elif isinstance(o, (str, UserString)):
|
|
139
|
-
return o.encode(encoding, errors)
|
|
140
|
-
try:
|
|
141
|
-
return bytes(o)
|
|
142
|
-
except TypeError:
|
|
143
|
-
return bytes(str(o), encoding, errors)
|
|
144
|
-
|
|
145
|
-
|
|
146
169
|
def encode_multipart_data(
|
|
147
170
|
data: None | Mapping[Buffer | str, Any] = None,
|
|
148
171
|
files: None | Mapping[Buffer | str, Any] = None,
|
|
@@ -330,3 +353,97 @@ def encode_multipart_data_async(
|
|
|
330
353
|
|
|
331
354
|
return {"content-type": "multipart/form-data; boundary="+boundary}, encode_iter()
|
|
332
355
|
|
|
356
|
+
|
|
357
|
+
def json_default(o, /):
|
|
358
|
+
if isinstance(o, Mapping):
|
|
359
|
+
return dict(o)
|
|
360
|
+
elif isinstance(o, Buffer):
|
|
361
|
+
return ensure_str(o)
|
|
362
|
+
elif isinstance(o, UserString):
|
|
363
|
+
return str(o)
|
|
364
|
+
elif isinstance(o, Integral):
|
|
365
|
+
return int(o)
|
|
366
|
+
elif isinstance(o, (Real, Fraction, Decimal)):
|
|
367
|
+
try:
|
|
368
|
+
return float(o)
|
|
369
|
+
except Exception:
|
|
370
|
+
return str(o)
|
|
371
|
+
elif isinstance(o, (Iterator, Sequence)):
|
|
372
|
+
return list(o)
|
|
373
|
+
else:
|
|
374
|
+
return str(o)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def normalize_request_args(
|
|
378
|
+
method: string,
|
|
379
|
+
url: string | SupportsGeturl | URL,
|
|
380
|
+
params: Any = None,
|
|
381
|
+
data: Any = None,
|
|
382
|
+
json: Any = None,
|
|
383
|
+
headers: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
384
|
+
ensure_ascii: bool = False,
|
|
385
|
+
) -> RequestArgs:
|
|
386
|
+
method = ensure_str(method).upper()
|
|
387
|
+
if isinstance(url, SupportsGeturl):
|
|
388
|
+
url = url.geturl()
|
|
389
|
+
elif isinstance(url, URL):
|
|
390
|
+
url = str(url)
|
|
391
|
+
url = complete_url(ensure_str(url))
|
|
392
|
+
if params and (params := urlencode(params)):
|
|
393
|
+
urlp = urlparse(url)
|
|
394
|
+
if query := urlp.query:
|
|
395
|
+
params = query + "&" + params
|
|
396
|
+
url = urlunparse(urlp._replace(query=params))
|
|
397
|
+
if ensure_ascii:
|
|
398
|
+
url = ensure_ascii_url(url)
|
|
399
|
+
headers_ = dict_map(
|
|
400
|
+
headers or (),
|
|
401
|
+
key=lambda k: ensure_str(k).lower(),
|
|
402
|
+
value=ensure_str,
|
|
403
|
+
)
|
|
404
|
+
content_type = headers_.get("content-type", "")
|
|
405
|
+
charset = get_charset(content_type)
|
|
406
|
+
mimetype = get_mimetype(charset).lower()
|
|
407
|
+
if data is not None:
|
|
408
|
+
if isinstance(data, Buffer):
|
|
409
|
+
pass
|
|
410
|
+
elif isinstance(data, (str, UserString)):
|
|
411
|
+
data = data.encode(charset)
|
|
412
|
+
elif isinstance(data, AsyncIterable):
|
|
413
|
+
data = async_map(ensure_buffer, data)
|
|
414
|
+
elif isinstance(data, Iterator):
|
|
415
|
+
data = map(ensure_buffer, data)
|
|
416
|
+
elif mimetype == "application/json":
|
|
417
|
+
if charset == "utf-8":
|
|
418
|
+
data = json_dumps(data, default=json_default)
|
|
419
|
+
else:
|
|
420
|
+
from json import dumps
|
|
421
|
+
data = dumps(data, default=json_default).encode(charset)
|
|
422
|
+
elif isinstance(data, (Mapping, Sequence)):
|
|
423
|
+
if data:
|
|
424
|
+
data = urlencode(data, charset).encode(charset)
|
|
425
|
+
if mimetype != "application/x-www-form-urlencoded":
|
|
426
|
+
headers_["content-type"] = "application/x-www-form-urlencoded"
|
|
427
|
+
else:
|
|
428
|
+
data = str(data).encode(charset)
|
|
429
|
+
elif json is not None:
|
|
430
|
+
if isinstance(json, Buffer):
|
|
431
|
+
data = json
|
|
432
|
+
elif isinstance(data, AsyncIterable):
|
|
433
|
+
data = async_map(ensure_buffer, data)
|
|
434
|
+
if charset == "utf-8":
|
|
435
|
+
data = json_dumps(data, default=json_default)
|
|
436
|
+
else:
|
|
437
|
+
from json import dumps
|
|
438
|
+
data = dumps(data, default=json_default).encode(charset)
|
|
439
|
+
if mimetype != "application/json":
|
|
440
|
+
headers_["content-type"] = "application/json; charset=" + charset
|
|
441
|
+
elif mimetype == "application/json":
|
|
442
|
+
data = b"null"
|
|
443
|
+
return {
|
|
444
|
+
"url": url,
|
|
445
|
+
"method": method,
|
|
446
|
+
"data": data,
|
|
447
|
+
"headers": headers_
|
|
448
|
+
}
|
|
449
|
+
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-http_request"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.9"
|
|
4
4
|
description = "Python http response utils."
|
|
5
5
|
authors = ["ChenyangGao <wosiwujm@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "readme.md"
|
|
8
|
-
homepage = "https://github.com/ChenyangGao/
|
|
9
|
-
repository = "https://github.com/ChenyangGao/
|
|
8
|
+
homepage = "https://github.com/ChenyangGao/python-modules/tree/main/python-http_request"
|
|
9
|
+
repository = "https://github.com/ChenyangGao/python-modules/tree/main/python-http_request"
|
|
10
10
|
keywords = ["http", "request"]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"License :: OSI Approved :: MIT License",
|
|
@@ -27,11 +27,13 @@ include = [
|
|
|
27
27
|
|
|
28
28
|
[tool.poetry.dependencies]
|
|
29
29
|
python = "^3.12"
|
|
30
|
+
http_response = ">=0.0.4"
|
|
31
|
+
orjson = "*"
|
|
30
32
|
python-asynctools = ">=0.1.3"
|
|
31
33
|
python-dicttools = ">=0.0.1"
|
|
34
|
+
python-ensure = ">=0.0.1"
|
|
32
35
|
python-filewrap = ">=0.2.8"
|
|
33
36
|
python-texttools = ">=0.0.4"
|
|
34
|
-
integer_tool = ">=0.0.5"
|
|
35
37
|
|
|
36
38
|
[build-system]
|
|
37
39
|
requires = ["poetry-core"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|