http_client_request 0.0.1__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.
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: http_client_request
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: http.client request extension.
|
|
5
|
+
Home-page: https://github.com/ChenyangGao/python-modules/tree/main/http_client_request
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: http.client,request
|
|
8
|
+
Author: ChenyangGao
|
|
9
|
+
Author-email: wosiwujm@gmail.com
|
|
10
|
+
Requires-Python: >=3.12,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Topic :: Software Development
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Dist: http_response (>=0.0.8)
|
|
24
|
+
Requires-Dist: python-argtools (>=0.0.2)
|
|
25
|
+
Requires-Dist: python-cookietools (>=0.0.11)
|
|
26
|
+
Requires-Dist: python-dicttools (>=0.0.2)
|
|
27
|
+
Requires-Dist: python-filewrap (>=0.2.8)
|
|
28
|
+
Requires-Dist: python-http_request (>=0.1.3)
|
|
29
|
+
Requires-Dist: yarl
|
|
30
|
+
Project-URL: Repository, https://github.com/ChenyangGao/python-modules/tree/main/http_client_request
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# http.client request extension.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
You can install via [pypi](https://pypi.org/project/http_client_request/)
|
|
38
|
+
|
|
39
|
+
```console
|
|
40
|
+
pip install -U http_client_request
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from http_client_request import request
|
|
47
|
+
```
|
|
48
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
|
|
4
|
+
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
5
|
+
__version__ = (0, 0, 1)
|
|
6
|
+
__all__ = ["request"]
|
|
7
|
+
|
|
8
|
+
from collections import UserString
|
|
9
|
+
from collections.abc import Buffer, Callable, Iterable, Mapping
|
|
10
|
+
from http.client import HTTPConnection, HTTPSConnection, HTTPResponse
|
|
11
|
+
from http.cookiejar import CookieJar
|
|
12
|
+
from http.cookies import SimpleCookie
|
|
13
|
+
from inspect import signature
|
|
14
|
+
from os import PathLike
|
|
15
|
+
from types import EllipsisType
|
|
16
|
+
from typing import cast, overload, Any, Final, Literal
|
|
17
|
+
from urllib.error import HTTPError
|
|
18
|
+
from urllib.parse import urljoin, urlsplit, urlunsplit
|
|
19
|
+
|
|
20
|
+
from argtools import argcount
|
|
21
|
+
from cookietools import cookies_to_str, extract_cookies
|
|
22
|
+
from dicttools import get_all_items
|
|
23
|
+
from filewrap import bio_chunk_iter, SupportsRead
|
|
24
|
+
from http_request import normalize_request_args, SupportsGeturl
|
|
25
|
+
from http_response import decompress_response, parse_response
|
|
26
|
+
from yarl import URL
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
type string = Buffer | str | UserString
|
|
30
|
+
|
|
31
|
+
HTTP_CONNECTION_KWARGS: Final = signature(HTTPConnection).parameters.keys()
|
|
32
|
+
HTTPS_CONNECTION_KWARGS: Final = signature(HTTPSConnection).parameters.keys()
|
|
33
|
+
|
|
34
|
+
if "__del__" not in HTTPConnection.__dict__:
|
|
35
|
+
setattr(HTTPConnection, "__del__", HTTPConnection.close)
|
|
36
|
+
if "__del__" not in HTTPSConnection.__dict__:
|
|
37
|
+
setattr(HTTPSConnection, "__del__", HTTPSConnection.close)
|
|
38
|
+
if "__del__" not in HTTPResponse.__dict__:
|
|
39
|
+
setattr(HTTPResponse, "__del__", HTTPResponse.close)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_host_pair(url: None | str, /) -> None | tuple[str, None | int]:
|
|
43
|
+
if not url:
|
|
44
|
+
return None
|
|
45
|
+
if not url.startswith(("http://", "https://")):
|
|
46
|
+
url = "http://" + url
|
|
47
|
+
urlp = urlsplit(url)
|
|
48
|
+
return urlp.hostname or "localhost", urlp.port
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def request(
|
|
53
|
+
url: string | SupportsGeturl | URL,
|
|
54
|
+
method: string = "GET",
|
|
55
|
+
params: None | string | Mapping | Iterable[tuple[Any, Any]] = None,
|
|
56
|
+
data: Any = None,
|
|
57
|
+
json: Any = None,
|
|
58
|
+
files: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
59
|
+
headers: None | Mapping[string, string] | Iterable[tuple[string, string]] = None,
|
|
60
|
+
follow_redirects: bool = True,
|
|
61
|
+
raise_for_status: bool = True,
|
|
62
|
+
cookies: None | CookieJar | SimpleCookie = None,
|
|
63
|
+
proxies: None | str | dict[str, str] = None,
|
|
64
|
+
*,
|
|
65
|
+
parse: None | EllipsisType = None,
|
|
66
|
+
**request_kwargs,
|
|
67
|
+
) -> HTTPResponse:
|
|
68
|
+
...
|
|
69
|
+
@overload
|
|
70
|
+
def request(
|
|
71
|
+
url: string | SupportsGeturl | URL,
|
|
72
|
+
method: string = "GET",
|
|
73
|
+
params: None | string | Mapping | Iterable[tuple[Any, Any]] = None,
|
|
74
|
+
data: Any = None,
|
|
75
|
+
json: Any = None,
|
|
76
|
+
files: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
77
|
+
headers: None | Mapping[string, string] | Iterable[tuple[string, string]] = None,
|
|
78
|
+
follow_redirects: bool = True,
|
|
79
|
+
raise_for_status: bool = True,
|
|
80
|
+
cookies: None | CookieJar | SimpleCookie = None,
|
|
81
|
+
proxies: None | str | dict[str, str] = None,
|
|
82
|
+
*,
|
|
83
|
+
parse: Literal[False],
|
|
84
|
+
**request_kwargs,
|
|
85
|
+
) -> bytes:
|
|
86
|
+
...
|
|
87
|
+
@overload
|
|
88
|
+
def request(
|
|
89
|
+
url: string | SupportsGeturl | URL,
|
|
90
|
+
method: string = "GET",
|
|
91
|
+
params: None | string | Mapping | Iterable[tuple[Any, Any]] = None,
|
|
92
|
+
data: Any = None,
|
|
93
|
+
json: Any = None,
|
|
94
|
+
files: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
95
|
+
headers: None | Mapping[string, string] | Iterable[tuple[string, string]] = None,
|
|
96
|
+
follow_redirects: bool = True,
|
|
97
|
+
raise_for_status: bool = True,
|
|
98
|
+
cookies: None | CookieJar | SimpleCookie = None,
|
|
99
|
+
proxies: None | str | dict[str, str] = None,
|
|
100
|
+
*,
|
|
101
|
+
parse: Literal[True],
|
|
102
|
+
**request_kwargs,
|
|
103
|
+
) -> bytes | str | dict | list | int | float | bool | None:
|
|
104
|
+
...
|
|
105
|
+
@overload
|
|
106
|
+
def request[T](
|
|
107
|
+
url: string | SupportsGeturl | URL,
|
|
108
|
+
method: string = "GET",
|
|
109
|
+
params: None | string | Mapping | Iterable[tuple[Any, Any]] = None,
|
|
110
|
+
data: Any = None,
|
|
111
|
+
json: Any = None,
|
|
112
|
+
files: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
113
|
+
headers: None | Mapping[string, string] | Iterable[tuple[string, string]] = None,
|
|
114
|
+
follow_redirects: bool = True,
|
|
115
|
+
raise_for_status: bool = True,
|
|
116
|
+
cookies: None | CookieJar | SimpleCookie = None,
|
|
117
|
+
proxies: None | str | dict[str, str] = None,
|
|
118
|
+
*,
|
|
119
|
+
parse: Callable[[HTTPResponse, bytes], T] | Callable[[HTTPResponse], T],
|
|
120
|
+
**request_kwargs,
|
|
121
|
+
) -> T:
|
|
122
|
+
...
|
|
123
|
+
def request[T](
|
|
124
|
+
url: string | SupportsGeturl | URL,
|
|
125
|
+
method: string = "GET",
|
|
126
|
+
params: None | string | Mapping | Iterable[tuple[Any, Any]] = None,
|
|
127
|
+
data: Any = None,
|
|
128
|
+
json: Any = None,
|
|
129
|
+
files: None | Mapping[string, Any] | Iterable[tuple[string, Any]] = None,
|
|
130
|
+
headers: None | Mapping[string, string] | Iterable[tuple[string, string]] = None,
|
|
131
|
+
follow_redirects: bool = True,
|
|
132
|
+
raise_for_status: bool = True,
|
|
133
|
+
cookies: None | CookieJar | SimpleCookie = None,
|
|
134
|
+
proxies: None | str | dict[str, str] = None,
|
|
135
|
+
*,
|
|
136
|
+
parse: None | EllipsisType| bool | Callable[[HTTPResponse, bytes], T] | Callable[[HTTPResponse], T] = None,
|
|
137
|
+
**request_kwargs,
|
|
138
|
+
) -> HTTPResponse | bytes | str | dict | list | int | float | bool | None | T:
|
|
139
|
+
if isinstance(proxies, str):
|
|
140
|
+
http_proxy = https_proxy = get_host_pair(proxies)
|
|
141
|
+
elif isinstance(proxies, dict):
|
|
142
|
+
http_proxy = get_host_pair(proxies.get("http"))
|
|
143
|
+
https_proxy = get_host_pair(proxies.get("https"))
|
|
144
|
+
if isinstance(data, PathLike):
|
|
145
|
+
data = bio_chunk_iter(open(data, "rb"))
|
|
146
|
+
elif isinstance(data, SupportsRead):
|
|
147
|
+
data = bio_chunk_iter(data)
|
|
148
|
+
request_args = normalize_request_args(
|
|
149
|
+
method=method,
|
|
150
|
+
url=url,
|
|
151
|
+
params=params,
|
|
152
|
+
data=data,
|
|
153
|
+
json=json,
|
|
154
|
+
files=files,
|
|
155
|
+
headers=headers,
|
|
156
|
+
)
|
|
157
|
+
url = request_args["url"]
|
|
158
|
+
headers_ = request_args["headers"]
|
|
159
|
+
need_set_cookie = "cookie" not in headers_
|
|
160
|
+
response_cookies = CookieJar()
|
|
161
|
+
connection: HTTPConnection | HTTPSConnection
|
|
162
|
+
while True:
|
|
163
|
+
if need_set_cookie:
|
|
164
|
+
if cookies:
|
|
165
|
+
headers_["cookie"] = cookies_to_str(cookies, url)
|
|
166
|
+
elif response_cookies:
|
|
167
|
+
headers_["cookie"] = cookies_to_str(response_cookies, url)
|
|
168
|
+
urlp = urlsplit(url)
|
|
169
|
+
request_kwargs["host"] = urlp.hostname
|
|
170
|
+
request_kwargs["port"] = urlp.port
|
|
171
|
+
if urlp.scheme == "https":
|
|
172
|
+
connection = HTTPSConnection(**dict(get_all_items(request_kwargs, *HTTPS_CONNECTION_KWARGS)))
|
|
173
|
+
if http_proxy:
|
|
174
|
+
connection.set_tunnel(*http_proxy)
|
|
175
|
+
else:
|
|
176
|
+
connection = HTTPConnection(**dict(get_all_items(request_kwargs, *HTTP_CONNECTION_KWARGS)))
|
|
177
|
+
if https_proxy:
|
|
178
|
+
connection.set_tunnel(*https_proxy)
|
|
179
|
+
connection.request(
|
|
180
|
+
request_args["method"],
|
|
181
|
+
urlunsplit(urlp._replace(scheme="", netloc="")),
|
|
182
|
+
cast(Buffer | Iterable[Buffer], request_args["data"]),
|
|
183
|
+
headers_,
|
|
184
|
+
)
|
|
185
|
+
response = connection.getresponse()
|
|
186
|
+
setattr(response, "connection", connection)
|
|
187
|
+
setattr(response, "url", url)
|
|
188
|
+
setattr(response, "cookies", response_cookies)
|
|
189
|
+
extract_cookies(response_cookies, url, response)
|
|
190
|
+
if cookies is not None:
|
|
191
|
+
extract_cookies(cookies, url, response) # type: ignore
|
|
192
|
+
status_code = response.status
|
|
193
|
+
if 300 <= status_code < 400 and follow_redirects:
|
|
194
|
+
url = request_args["url"] = urljoin(url, response.headers["location"])
|
|
195
|
+
if status_code == 303:
|
|
196
|
+
request_args["method"] = "GET"
|
|
197
|
+
request_args["data"] = None
|
|
198
|
+
continue
|
|
199
|
+
if status_code >= 400 and raise_for_status:
|
|
200
|
+
raise HTTPError(
|
|
201
|
+
url,
|
|
202
|
+
status_code,
|
|
203
|
+
response.reason,
|
|
204
|
+
response.headers,
|
|
205
|
+
response,
|
|
206
|
+
)
|
|
207
|
+
if parse is None:
|
|
208
|
+
return response
|
|
209
|
+
elif parse is ...:
|
|
210
|
+
response.close()
|
|
211
|
+
return response
|
|
212
|
+
if isinstance(parse, bool):
|
|
213
|
+
content = decompress_response(response.read())
|
|
214
|
+
if parse:
|
|
215
|
+
return parse_response(response, content)
|
|
216
|
+
return content
|
|
217
|
+
ac = argcount(parse)
|
|
218
|
+
if ac == 1:
|
|
219
|
+
return cast(Callable[[HTTPResponse], T], parse)(response)
|
|
220
|
+
else:
|
|
221
|
+
return cast(Callable[[HTTPResponse, bytes], T], parse)(
|
|
222
|
+
response, decompress_response(response.read()))
|
|
223
|
+
|
|
224
|
+
# TODO: 支持连接池
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "http_client_request"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "http.client request extension."
|
|
5
|
+
authors = ["ChenyangGao <wosiwujm@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "readme.md"
|
|
8
|
+
homepage = "https://github.com/ChenyangGao/python-modules/tree/main/http_client_request"
|
|
9
|
+
repository = "https://github.com/ChenyangGao/python-modules/tree/main/http_client_request"
|
|
10
|
+
keywords = ["http.client", "request"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Development Status :: 5 - Production/Stable",
|
|
14
|
+
"Programming Language :: Python",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Topic :: Software Development",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
]
|
|
24
|
+
include = [
|
|
25
|
+
"LICENSE",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.poetry.dependencies]
|
|
29
|
+
python = "^3.12"
|
|
30
|
+
http_response = ">=0.0.8"
|
|
31
|
+
python-argtools = ">=0.0.2"
|
|
32
|
+
python-cookietools = ">=0.0.11"
|
|
33
|
+
python-dicttools = ">=0.0.2"
|
|
34
|
+
python-filewrap = ">=0.2.8"
|
|
35
|
+
python-http_request = ">=0.1.3"
|
|
36
|
+
yarl = "*"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["poetry-core"]
|
|
40
|
+
build-backend = "poetry.core.masonry.api"
|
|
41
|
+
|
|
42
|
+
[[tool.poetry.packages]]
|
|
43
|
+
include = "http_client_request"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# http.client request extension.
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
You can install via [pypi](https://pypi.org/project/http_client_request/)
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
pip install -U http_client_request
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from http_client_request import request
|
|
15
|
+
```
|