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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-urlopen
3
- Version: 0.1.5.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.2)
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.5.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.2"
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)
5
+ __version__ = (0, 1, 6)
6
6
  __all__ = ["urlopen", "request", "download"]
7
7
 
8
- from collections import defaultdict, deque, UserString
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
- pass
125
+ pool: ConnectionPool = CONNECTION_POOL
230
126
 
231
127
 
232
128
  class KeepAliveHTTPSHandler(HTTPSHandler, KeepAliveBaseHTTPHandler):
233
- pass
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
- if context is None:
313
- if opener is None:
314
- handlers.append(copy(_https_handler))
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
- for i, handler in enumerate(handlers):
317
- if isinstance(handler, KeepAliveHTTPSHandler):
318
- break
319
- elif isinstance(handler, HTTPSHandler):
320
- handlers[i] = copy(_https_handler)
321
- break
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
- https_handler = KeepAliveHTTPSHandler(context=context)
326
- https_handler.pool = _pool
327
- if opener is None:
328
- handlers.append(https_handler)
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
- for i, handler in enumerate(handlers):
331
- if isinstance(handler, HTTPSHandler):
332
- handlers[i] = https_handler
333
- break
334
- else:
335
- handlers.append(https_handler)
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] = copy(_http_handler)
243
+ handler = handlers[i] = KeepAliveHTTPHandler(
244
+ debuglevel=getattr(handler, "_debuglevel"))
344
245
  break
345
246
  else:
346
- handlers.append(copy(_http_handler))
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
- handlers.append(HTTPCookieProcessor(cookies))
255
+ add_handler(HTTPCookieProcessor(cookies))
352
256
  response_cookies = CookieJar()
353
257
  if cookies is None:
354
258
  cookies = response_cookies
355
- handlers.append(HTTPCookieProcessor(response_cookies))
259
+ add_handler(HTTPCookieProcessor(response_cookies))
356
260
  if not follow_redirects:
357
- handlers.append(NoRedirectHandler())
261
+ add_handler(NoRedirectHandler())
358
262
  opener = build_opener(*handlers)
359
263
  setattr(opener, "cookies", cookies)
360
264
  try: