http_client_request 0.1.0__tar.gz → 0.1.2__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.4
2
2
  Name: http_client_request
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: http.client request extension.
5
5
  License: MIT
6
6
  Keywords: http.client,request
@@ -21,13 +21,11 @@ Classifier: Topic :: Software Development
21
21
  Classifier: Topic :: Software Development :: Libraries
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Requires-Dist: http_response (>=0.0.9)
24
- Requires-Dist: python-argtools (>=0.0.2)
25
24
  Requires-Dist: python-cookietools (>=0.1.4)
26
- Requires-Dist: python-dicttools (>=0.0.4)
25
+ Requires-Dist: python-dicttools (>=0.0.5)
27
26
  Requires-Dist: python-filewrap (>=0.2.9)
28
27
  Requires-Dist: python-http_request (>=0.1.6)
29
- Requires-Dist: python-property (>=0.0.3)
30
- Requires-Dist: python-undefined (>=0.0.3)
28
+ Requires-Dist: python-undefined (>=0.0.4)
31
29
  Requires-Dist: socket_keepalive (>=0.0.1)
32
30
  Requires-Dist: urllib3
33
31
  Requires-Dist: yarl
@@ -4,14 +4,12 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
7
- __version__ = (0, 1, 0)
7
+ __version__ = (0, 1, 2)
8
8
  __all__ = [
9
9
  "CONNECTION_POOL", "HTTPConnection", "HTTPSConnection", "HTTPResponse",
10
10
  "ConnectionPool", "request",
11
11
  ]
12
12
 
13
- import socket
14
-
15
13
  from array import array
16
14
  from collections import defaultdict, deque, UserString
17
15
  from collections.abc import Buffer, Callable, Iterable, Mapping
@@ -21,25 +19,21 @@ from http.client import (
21
19
  )
22
20
  from http.cookiejar import CookieJar
23
21
  from http.cookies import BaseCookie
24
- from io import BufferedReader
25
22
  from inspect import signature
26
23
  from os import PathLike
27
24
  from select import select
28
- from socket import socket as Socket
29
- from ssl import SSLWantReadError
25
+ from socket import socket
30
26
  from types import EllipsisType
31
27
  from typing import cast, overload, Any, Final, Literal
32
28
  from urllib.error import HTTPError
33
29
  from urllib.parse import urljoin, urlsplit, urlunsplit, ParseResult, SplitResult
34
30
  from warnings import warn
35
31
 
36
- from argtools import argcount
37
32
  from cookietools import cookies_to_str, extract_cookies
38
33
  from dicttools import get_all_items
39
34
  from filewrap import SupportsRead
40
35
  from http_request import normalize_request_args, SupportsGeturl
41
- from http_response import decompress_response, parse_response
42
- from property import funcproperty
36
+ from http_response import decompress_response, parse_response, get_length
43
37
  from socket_keepalive import socket_keepalive
44
38
  from urllib3 import HTTPResponse as Urllib3HTTPResponse, HTTPHeaderDict
45
39
  from undefined import undefined, Undefined
@@ -70,7 +64,7 @@ def is_ipv6(host: str, /) -> bool:
70
64
  return False
71
65
 
72
66
 
73
- def sock_buf_readable(sock: Socket, /) -> bool:
67
+ def sock_buf_readable(sock: socket, /) -> bool:
74
68
  rlist, *_ = select([sock], (), (), 0)
75
69
  return bool(rlist)
76
70
 
@@ -80,6 +74,8 @@ try:
80
74
  from termios import FIONREAD
81
75
 
82
76
  def sock_bufsize(sock, /) -> int:
77
+ if sock is None:
78
+ return 0
83
79
  sock_size = array("i", [0])
84
80
  ioctl(sock, FIONREAD, sock_size)
85
81
  return sock_size[0]
@@ -87,6 +83,8 @@ except ImportError:
87
83
  from ctypes import byref, c_ulong, WinDLL # type: ignore
88
84
  ws2_32 = WinDLL("ws2_32")
89
85
  def sock_bufsize(sock, /) -> int:
86
+ if sock is None:
87
+ return 0
90
88
  FIONREAD = 0x4004667f
91
89
  b = c_ulong(0)
92
90
  ws2_32.ioctlsocket(sock.fileno(), FIONREAD, byref(b))
@@ -102,70 +100,22 @@ class HTTPResponse(BaseHTTPResponse):
102
100
  def __del__(self, /):
103
101
  self.close()
104
102
 
105
- @funcproperty
106
- def _fp(self, /) -> BufferedReader:
107
- return self.fp
108
-
109
- @property
110
- def buffer_size(self, /) -> int:
111
- fp = self._fp
112
- sock = fp.raw._sock
113
- sock.setblocking(False)
114
- try:
115
- cache_size = len(fp.peek() or b"")
116
- while True:
117
- buffer_size = cache_size + sock_bufsize(sock)
118
- if cache_size == (cache_size := len(fp.peek() or b"")):
119
- return buffer_size
120
- except (BlockingIOError, SSLWantReadError):
121
- return 0
122
- finally:
123
- sock.setblocking(True)
124
-
125
- @property
126
- def unbuffer_size(self, /) -> int:
127
- method = self.__dict__.get("method", "").upper()
128
- content_length = self.length
129
- if method == "HEAD" or content_length == 0:
130
- return 0
131
- if content_length:
132
- return content_length - self.tell() - self.buffer_size
133
- sock = self._fp.raw._sock
134
- if sock_bufsize(sock) == sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF):
135
- return -1
136
- else:
137
- return 0
138
-
139
- @property
140
- def unread_size(self, /) -> int:
141
- method = self.__dict__.get("method", "").upper()
142
- content_length = self.length
143
- if method == "HEAD" or content_length == 0:
144
- return 0
145
- sock = self._fp.raw._sock
146
- if content_length:
147
- return content_length - self.tell()
148
- elif sock_bufsize(sock) == sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF):
149
- return -1
150
- else:
151
- return self.buffer_size
152
-
153
103
  def _close_conn(self, /):
154
- fp = self._fp = self.fp
104
+ fp = self.fp
155
105
  setattr(self, "fp", None)
156
- try:
157
- pool = self.pool
158
- connection = self.connection
159
- if pool and connection:
160
- if not 200 <= self.status < 300:
161
- try:
162
- self.read()
163
- except OSError:
164
- pass
165
- pool.return_connection(connection)
166
- else:
167
- fp.close()
168
- except AttributeError:
106
+ pool = getattr(self, "pool", None)
107
+ connection = getattr(self, "connection", None)
108
+ if pool and connection and not (
109
+ 200 <= self.status < 300 and
110
+ (length := self.length) and
111
+ length - self._pos - len(fp.peek()) - sock_bufsize(connection.sock) > 1024 * 1024 * 10
112
+ ):
113
+ try:
114
+ self.read()
115
+ except OSError:
116
+ pass
117
+ pool.return_connection(connection)
118
+ else:
169
119
  fp.close()
170
120
 
171
121
  def get_urllib3_response(self, /) -> Urllib3HTTPResponse:
@@ -311,35 +261,8 @@ class ConnectionPool:
311
261
  except IndexError:
312
262
  break
313
263
  con.timeout = timeout
314
- sock = con.sock
315
- resp = con.response
316
- if con.state == "Idle" or not sock:
317
- pass
318
- elif con.state == "Request-sent" or getattr(sock, "_closed"):
264
+ if con.state != "Idle" or getattr(con.sock, "_closed"):
319
265
  con.close()
320
- elif resp and 0 < resp.unbuffer_size <= 1024 * 1024:
321
- try:
322
- resp.read()
323
- except (ConnectionResetError, BrokenPipeError):
324
- con.close()
325
- else:
326
- try:
327
- sock.setblocking(False)
328
- except OSError:
329
- con.close()
330
- else:
331
- try:
332
- if Socket.recv(sock, 1):
333
- con.close()
334
- except BlockingIOError:
335
- pass
336
- except (ConnectionResetError, BrokenPipeError):
337
- con.close()
338
- try:
339
- if not getattr(sock, "_closed"):
340
- sock.setblocking(True)
341
- except OSError:
342
- pass
343
266
  return con
344
267
  if url.scheme == "https":
345
268
  return HTTPSConnection(url.hostname or "localhost", url.port, timeout=timeout)
@@ -440,7 +363,7 @@ def request[T](
440
363
  proxies: None | str | dict[str, str] = None,
441
364
  pool: None | Undefined | ConnectionPool = undefined,
442
365
  *,
443
- parse: Callable[[HTTPResponse, bytes], T] | Callable[[HTTPResponse], T],
366
+ parse: Callable[[HTTPResponse, bytes], T],
444
367
  **request_kwargs,
445
368
  ) -> T:
446
369
  ...
@@ -458,7 +381,7 @@ def request[T](
458
381
  proxies: None | str | dict[str, str] = None,
459
382
  pool: None | Undefined | ConnectionPool = undefined,
460
383
  *,
461
- parse: None | EllipsisType| bool | Callable[[HTTPResponse, bytes], T] | Callable[[HTTPResponse], T] = None,
384
+ parse: None | EllipsisType| bool | Callable[[HTTPResponse, bytes], T] = None,
462
385
  **request_kwargs,
463
386
  ) -> HTTPResponse | bytes | str | dict | list | int | float | bool | None | T:
464
387
  if pool is undefined:
@@ -561,8 +484,10 @@ def request[T](
561
484
  if status_code == 303:
562
485
  method = "GET"
563
486
  body = None
487
+ response.read()
564
488
  continue
565
489
  elif status_code >= 400 and raise_for_status:
490
+ setattr(response, "content", response.read())
566
491
  raise HTTPError(
567
492
  url,
568
493
  status_code,
@@ -571,21 +496,23 @@ def request[T](
571
496
  response,
572
497
  )
573
498
  if parse is None:
499
+ if method == "HEAD":
500
+ response.read()
574
501
  return response
575
502
  elif parse is ...:
576
- response.close()
503
+ try:
504
+ if (method == "HEAD" or
505
+ (length := get_length(response)) is not None and length <= 10485760
506
+ ):
507
+ response.read()
508
+ finally:
509
+ response.close()
577
510
  return response
511
+ content = decompress_response(response.read(), response)
578
512
  if isinstance(parse, bool):
579
- content = decompress_response(response.read(), response)
580
- if parse:
581
- return parse_response(response, content)
582
- return content
583
- ac = argcount(parse)
584
- if ac == 1:
585
- return cast(Callable[[HTTPResponse], T], parse)(response)
586
- else:
587
- content = decompress_response(response.read(), response)
588
- return cast(Callable[[HTTPResponse, bytes], T], parse)(
589
- response, content)
513
+ if not parse:
514
+ return content
515
+ parse = cast(Callable, parse_response)
516
+ return parse(response, content)
590
517
 
591
518
  # TODO: 实现异步请求,非阻塞模式(sock.setblocking(False)),对于响应体的数据加载,使用 select 模块进行通知
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "http_client_request"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "http.client request extension."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -28,13 +28,11 @@ include = [
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.12"
30
30
  http_response = ">=0.0.9"
31
- python-argtools = ">=0.0.2"
32
31
  python-cookietools = ">=0.1.4"
33
- python-dicttools = ">=0.0.4"
32
+ python-dicttools = ">=0.0.5"
34
33
  python-filewrap = ">=0.2.9"
35
34
  python-http_request = ">=0.1.6"
36
- python-property = ">=0.0.3"
37
- python-undefined = ">=0.0.3"
35
+ python-undefined = ">=0.0.4"
38
36
  socket_keepalive = ">=0.0.1"
39
37
  urllib3 = "*"
40
38
  yarl = "*"