python-urlopen 0.1.5.1__tar.gz → 0.1.6.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.
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/PKG-INFO +3 -2
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/pyproject.toml +3 -2
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/urlopen/__init__.py +49 -145
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/LICENSE +0 -0
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/readme.md +0 -0
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/urlopen/__main__.py +0 -0
- {python_urlopen-0.1.5.1 → python_urlopen-0.1.6.1}/urlopen/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-urlopen
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6.1
|
|
4
4
|
Summary: Python urlopen wrapper.
|
|
5
5
|
Home-page: https://github.com/ChenyangGao/python-modules/tree/main/python-urlopen
|
|
6
6
|
License: MIT
|
|
@@ -20,9 +20,10 @@ 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: http_client_request (>=0.0.6)
|
|
23
24
|
Requires-Dist: http_response (>=0.0.9)
|
|
24
25
|
Requires-Dist: python-argtools (>=0.0.2)
|
|
25
|
-
Requires-Dist: python-cookietools (>=0.1.
|
|
26
|
+
Requires-Dist: python-cookietools (>=0.1.3)
|
|
26
27
|
Requires-Dist: python-dicttools (>=0.0.4)
|
|
27
28
|
Requires-Dist: python-filewrap (>=0.2.8)
|
|
28
29
|
Requires-Dist: python-http_request (>=0.1.4)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-urlopen"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.6.1"
|
|
4
4
|
description = "Python urlopen wrapper."
|
|
5
5
|
authors = ["ChenyangGao <wosiwujm@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -27,9 +27,10 @@ include = [
|
|
|
27
27
|
|
|
28
28
|
[tool.poetry.dependencies]
|
|
29
29
|
python = "^3.12"
|
|
30
|
+
http_client_request = ">=0.0.6"
|
|
30
31
|
http_response = ">=0.0.9"
|
|
31
32
|
python-argtools = ">=0.0.2"
|
|
32
|
-
python-cookietools = ">=0.1.
|
|
33
|
+
python-cookietools = ">=0.1.3"
|
|
33
34
|
python-dicttools = ">=0.0.4"
|
|
34
35
|
python-filewrap = ">=0.2.8"
|
|
35
36
|
python-http_request = ">=0.1.4"
|
|
@@ -2,25 +2,22 @@
|
|
|
2
2
|
# coding: utf-8
|
|
3
3
|
|
|
4
4
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
5
|
-
__version__ = (0, 1,
|
|
5
|
+
__version__ = (0, 1, 6)
|
|
6
6
|
__all__ = ["urlopen", "request", "download"]
|
|
7
7
|
|
|
8
|
-
from collections import
|
|
8
|
+
from collections import UserString
|
|
9
9
|
from collections.abc import Buffer, Callable, Generator, Iterable, Mapping
|
|
10
10
|
from copy import copy
|
|
11
|
-
from http.client import HTTPConnection, HTTPSConnection, HTTPResponse
|
|
12
11
|
from http.cookiejar import CookieJar
|
|
13
12
|
from http.cookies import BaseCookie
|
|
14
13
|
from inspect import isgenerator
|
|
15
14
|
from os import fsdecode, fstat, makedirs, PathLike
|
|
16
15
|
from os.path import abspath, dirname, isdir, join as joinpath
|
|
17
16
|
from shutil import COPY_BUFSIZE # type: ignore
|
|
18
|
-
from socket import socket
|
|
19
17
|
from ssl import SSLContext, _create_unverified_context
|
|
20
18
|
from types import EllipsisType
|
|
21
19
|
from typing import cast, overload, Any, Literal
|
|
22
20
|
from urllib.error import HTTPError, URLError
|
|
23
|
-
from urllib.parse import urlsplit, ParseResult, SplitResult
|
|
24
21
|
from urllib.request import (
|
|
25
22
|
build_opener, AbstractHTTPHandler, BaseHandler, HTTPHandler,
|
|
26
23
|
HTTPSHandler, HTTPRedirectHandler, OpenerDirector, Request,
|
|
@@ -30,6 +27,7 @@ from argtools import argcount
|
|
|
30
27
|
from cookietools import cookies_to_str, extract_cookies, update_cookies
|
|
31
28
|
from dicttools import iter_items
|
|
32
29
|
from filewrap import bio_skip_iter, SupportsRead, SupportsWrite
|
|
30
|
+
from http_client_request import ConnectionPool, HTTPResponse, CONNECTION_POOL
|
|
33
31
|
from http_request import normalize_request_args, SupportsGeturl
|
|
34
32
|
from http_response import (
|
|
35
33
|
decompress_response, get_filename, get_length, is_chunked, is_range_request,
|
|
@@ -42,39 +40,9 @@ from undefined import undefined, Undefined
|
|
|
42
40
|
|
|
43
41
|
type string = Buffer | str | UserString
|
|
44
42
|
|
|
45
|
-
if "__del__" not in HTTPConnection.__dict__:
|
|
46
|
-
setattr(HTTPConnection, "__del__", HTTPConnection.close)
|
|
47
|
-
if "__del__" not in HTTPSConnection.__dict__:
|
|
48
|
-
setattr(HTTPSConnection, "__del__", HTTPSConnection.close)
|
|
49
|
-
if "__del__" not in HTTPResponse.__dict__:
|
|
50
|
-
setattr(HTTPResponse, "__del__", HTTPResponse.close)
|
|
51
43
|
if "__del__" not in OpenerDirector.__dict__:
|
|
52
44
|
setattr(OpenerDirector, "__del__", OpenerDirector.close)
|
|
53
45
|
|
|
54
|
-
def _close_conn(self, /):
|
|
55
|
-
fp = self.fp
|
|
56
|
-
self.fp = None
|
|
57
|
-
pool = getattr(self, "pool", None)
|
|
58
|
-
conn = getattr(self, "connection", None)
|
|
59
|
-
if pool and conn:
|
|
60
|
-
try:
|
|
61
|
-
pool.return_connection(conn)
|
|
62
|
-
except NameError:
|
|
63
|
-
pass
|
|
64
|
-
else:
|
|
65
|
-
fp.close()
|
|
66
|
-
|
|
67
|
-
setattr(HTTPResponse, "_close_conn", _close_conn)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def is_ipv6(host: str, /) -> bool:
|
|
71
|
-
from ipaddress import _BaseV6, AddressValueError
|
|
72
|
-
try:
|
|
73
|
-
_BaseV6._ip_int_from_string(host) # type: ignore
|
|
74
|
-
return True
|
|
75
|
-
except AddressValueError:
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
46
|
|
|
79
47
|
class HTTPCookieProcessor(BaseHandler):
|
|
80
48
|
|
|
@@ -103,82 +71,6 @@ class HTTPCookieProcessor(BaseHandler):
|
|
|
103
71
|
https_response = http_response
|
|
104
72
|
|
|
105
73
|
|
|
106
|
-
class ConnectionPool:
|
|
107
|
-
|
|
108
|
-
def __init__(
|
|
109
|
-
self,
|
|
110
|
-
/,
|
|
111
|
-
pool: None | defaultdict[str, deque[HTTPConnection] | deque[HTTPSConnection]] = None,
|
|
112
|
-
):
|
|
113
|
-
if pool is None:
|
|
114
|
-
pool = defaultdict(deque)
|
|
115
|
-
self.pool = pool
|
|
116
|
-
|
|
117
|
-
def __del__(self, /):
|
|
118
|
-
for dq in self.pool.values():
|
|
119
|
-
for con in dq:
|
|
120
|
-
con.close()
|
|
121
|
-
|
|
122
|
-
def __repr__(self, /) -> str:
|
|
123
|
-
cls = type(self)
|
|
124
|
-
return f"{cls.__module__}.{cls.__qualname__}({self.pool!r})"
|
|
125
|
-
|
|
126
|
-
def get_connection(
|
|
127
|
-
self,
|
|
128
|
-
/,
|
|
129
|
-
url: str | ParseResult | SplitResult,
|
|
130
|
-
timeout: None | float = None,
|
|
131
|
-
) -> HTTPConnection | HTTPSConnection:
|
|
132
|
-
if isinstance(url, str):
|
|
133
|
-
url = urlsplit(url)
|
|
134
|
-
assert url.scheme, "not a complete URL"
|
|
135
|
-
host = url.hostname or "localhost"
|
|
136
|
-
if is_ipv6(host):
|
|
137
|
-
host = f"[{host}]"
|
|
138
|
-
port = url.port or (443 if url.scheme == 'https' else 80)
|
|
139
|
-
origin = f"{url.scheme}://{host}:{port}"
|
|
140
|
-
dq = self.pool[origin]
|
|
141
|
-
while True:
|
|
142
|
-
try:
|
|
143
|
-
con = dq.popleft()
|
|
144
|
-
except IndexError:
|
|
145
|
-
break
|
|
146
|
-
sock = con.sock
|
|
147
|
-
if not sock or getattr(sock, "_closed"):
|
|
148
|
-
con.connect()
|
|
149
|
-
else:
|
|
150
|
-
sock.setblocking(False)
|
|
151
|
-
try:
|
|
152
|
-
if socket.recv(sock, 1):
|
|
153
|
-
con.connect()
|
|
154
|
-
except BlockingIOError:
|
|
155
|
-
pass
|
|
156
|
-
finally:
|
|
157
|
-
sock.setblocking(True)
|
|
158
|
-
con.timeout = timeout
|
|
159
|
-
return con
|
|
160
|
-
if url.scheme == "https":
|
|
161
|
-
return HTTPSConnection(url.hostname or "localhost", url.port, timeout=timeout)
|
|
162
|
-
else:
|
|
163
|
-
return HTTPConnection(url.hostname or "localhost", url.port, timeout=timeout)
|
|
164
|
-
|
|
165
|
-
def return_connection(
|
|
166
|
-
self,
|
|
167
|
-
con: HTTPConnection | HTTPSConnection,
|
|
168
|
-
/,
|
|
169
|
-
) -> str:
|
|
170
|
-
if isinstance(con, HTTPSConnection):
|
|
171
|
-
scheme = "https"
|
|
172
|
-
else:
|
|
173
|
-
scheme = "http"
|
|
174
|
-
host = con.host
|
|
175
|
-
if is_ipv6(host):
|
|
176
|
-
host = f"[{host}]"
|
|
177
|
-
origin = f"{scheme}://{host}:{con.port}"
|
|
178
|
-
self.pool[origin].append(con) # type: ignore
|
|
179
|
-
return origin
|
|
180
|
-
|
|
181
|
-
|
|
182
74
|
class KeepAliveBaseHTTPHandler(AbstractHTTPHandler):
|
|
183
75
|
|
|
184
76
|
@locked_cacheproperty
|
|
@@ -207,6 +99,10 @@ class KeepAliveBaseHTTPHandler(AbstractHTTPHandler):
|
|
|
207
99
|
tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
|
|
208
100
|
del headers[proxy_auth_hdr]
|
|
209
101
|
h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
|
|
102
|
+
else:
|
|
103
|
+
h._tunnel_host = None
|
|
104
|
+
h._tunnel_port = None
|
|
105
|
+
h._tunnel_headers.clear()
|
|
210
106
|
try:
|
|
211
107
|
try:
|
|
212
108
|
h.request(req.get_method(), req.selector, req.data, headers,
|
|
@@ -226,18 +122,15 @@ class KeepAliveBaseHTTPHandler(AbstractHTTPHandler):
|
|
|
226
122
|
|
|
227
123
|
|
|
228
124
|
class KeepAliveHTTPHandler(HTTPHandler, KeepAliveBaseHTTPHandler):
|
|
229
|
-
|
|
125
|
+
pool: ConnectionPool = CONNECTION_POOL
|
|
230
126
|
|
|
231
127
|
|
|
232
128
|
class KeepAliveHTTPSHandler(HTTPSHandler, KeepAliveBaseHTTPHandler):
|
|
233
|
-
|
|
129
|
+
pool: ConnectionPool = CONNECTION_POOL
|
|
234
130
|
|
|
235
131
|
|
|
236
|
-
_pool = ConnectionPool()
|
|
237
132
|
_http_handler = KeepAliveHTTPHandler()
|
|
238
|
-
_http_handler.pool = _pool
|
|
239
133
|
_https_handler = KeepAliveHTTPSHandler(context=_create_unverified_context())
|
|
240
|
-
_https_handler.pool = _pool
|
|
241
134
|
_cookies = CookieJar()
|
|
242
135
|
_opener: OpenerDirector = build_opener(
|
|
243
136
|
_http_handler,
|
|
@@ -267,6 +160,7 @@ def urlopen(
|
|
|
267
160
|
cookies: None | CookieJar | BaseCookie = None,
|
|
268
161
|
timeout: None | Undefined | float = undefined,
|
|
269
162
|
opener: None | OpenerDirector = _opener,
|
|
163
|
+
pool: None | ConnectionPool = None,
|
|
270
164
|
**_,
|
|
271
165
|
) -> HTTPResponse:
|
|
272
166
|
if isinstance(url, Request):
|
|
@@ -309,52 +203,62 @@ def urlopen(
|
|
|
309
203
|
cookies = getattr(opener, "cookies", None)
|
|
310
204
|
if cookies and "cookie" not in headers_:
|
|
311
205
|
headers_["cookie"] = cookies_to_str(cookies)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
206
|
+
add_handler = handlers.append
|
|
207
|
+
if opener is None:
|
|
208
|
+
http_handler = copy(_http_handler)
|
|
209
|
+
if context is None:
|
|
210
|
+
https_handler = copy(_https_handler)
|
|
315
211
|
else:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
else:
|
|
323
|
-
handlers.append(copy(_https_handler))
|
|
212
|
+
https_handler = KeepAliveHTTPSHandler(context=context)
|
|
213
|
+
if pool is not None:
|
|
214
|
+
http_handler.pool = pool
|
|
215
|
+
https_handler.pool = pool
|
|
216
|
+
add_handler(http_handler)
|
|
217
|
+
add_handler(https_handler)
|
|
324
218
|
else:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
219
|
+
for i, handler in enumerate(handlers):
|
|
220
|
+
if isinstance(handler, KeepAliveHTTPSHandler):
|
|
221
|
+
handler = handlers[i] = copy(handler)
|
|
222
|
+
if context is not None:
|
|
223
|
+
setattr(handler, "_context", context)
|
|
224
|
+
break
|
|
225
|
+
elif isinstance(handler, HTTPSHandler):
|
|
226
|
+
handler = handlers[i] = KeepAliveHTTPSHandler(
|
|
227
|
+
debuglevel=getattr(handler, "_debuglevel"),
|
|
228
|
+
context=getattr(handler, "_context") if context is None else context,
|
|
229
|
+
)
|
|
230
|
+
break
|
|
329
231
|
else:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if opener is None:
|
|
337
|
-
handlers.append(copy(_http_handler))
|
|
338
|
-
else:
|
|
232
|
+
handler = copy(_https_handler)
|
|
233
|
+
if context is not None:
|
|
234
|
+
setattr(handler, "_context", context)
|
|
235
|
+
add_handler(handler)
|
|
236
|
+
if pool is not None:
|
|
237
|
+
handler.pool = pool
|
|
339
238
|
for i, handler in enumerate(handlers):
|
|
340
239
|
if isinstance(handler, KeepAliveHTTPHandler):
|
|
240
|
+
handler = handlers[i] = copy(handler)
|
|
341
241
|
break
|
|
342
242
|
elif isinstance(handler, HTTPHandler):
|
|
343
|
-
handlers[i] =
|
|
243
|
+
handler = handlers[i] = KeepAliveHTTPHandler(
|
|
244
|
+
debuglevel=getattr(handler, "_debuglevel"))
|
|
344
245
|
break
|
|
345
246
|
else:
|
|
346
|
-
|
|
247
|
+
handler = copy(_http_handler)
|
|
248
|
+
add_handler(handler)
|
|
249
|
+
if pool is not None:
|
|
250
|
+
handler.pool = pool
|
|
347
251
|
if cookies and (opener is None or all(
|
|
348
252
|
h.cookies is not cookies
|
|
349
253
|
for h in getattr(opener, "handlers") if isinstance(h, HTTPCookieProcessor)
|
|
350
254
|
)):
|
|
351
|
-
|
|
255
|
+
add_handler(HTTPCookieProcessor(cookies))
|
|
352
256
|
response_cookies = CookieJar()
|
|
353
257
|
if cookies is None:
|
|
354
258
|
cookies = response_cookies
|
|
355
|
-
|
|
259
|
+
add_handler(HTTPCookieProcessor(response_cookies))
|
|
356
260
|
if not follow_redirects:
|
|
357
|
-
|
|
261
|
+
add_handler(NoRedirectHandler())
|
|
358
262
|
opener = build_opener(*handlers)
|
|
359
263
|
setattr(opener, "cookies", cookies)
|
|
360
264
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|