python-urlopen 0.0.5__tar.gz → 0.0.5.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.1
2
2
  Name: python-urlopen
3
- Version: 0.0.5
3
+ Version: 0.0.5.2
4
4
  Summary: Python urlopen wrapper.
5
5
  Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-urlopen
6
6
  License: MIT
@@ -21,9 +21,11 @@ Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Topic :: Software Development
22
22
  Classifier: Topic :: Software Development :: Libraries
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Dist: brotli
24
25
  Requires-Dist: http_response
25
26
  Requires-Dist: python-argtools
26
27
  Requires-Dist: python-filewrap
28
+ Requires-Dist: zstandard
27
29
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-urlopen
28
30
  Description-Content-Type: text/markdown
29
31
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-urlopen"
3
- version = "0.0.5"
3
+ version = "0.0.5.2"
4
4
  description = "Python urlopen wrapper."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -27,9 +27,11 @@ include = [
27
27
 
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.10"
30
+ brotli = "*"
30
31
  http_response = "*"
31
32
  python-argtools = "*"
32
33
  python-filewrap = "*"
34
+ zstandard = "*"
33
35
 
34
36
  [tool.poetry.scripts]
35
37
  python-urlopen = "urlopen.__main__:main"
@@ -9,6 +9,7 @@ import errno
9
9
 
10
10
  from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
11
11
  from copy import copy
12
+ from gzip import decompress as decompress_gzip
12
13
  from http.client import HTTPResponse
13
14
  from http.cookiejar import CookieJar
14
15
  from inspect import isgenerator
@@ -22,18 +23,60 @@ from typing import cast, Any
22
23
  from urllib.error import HTTPError
23
24
  from urllib.parse import urlencode, urlsplit
24
25
  from urllib.request import build_opener, HTTPCookieProcessor, HTTPSHandler, OpenerDirector, Request
26
+ from zlib import compressobj, DEF_MEM_LEVEL, DEFLATED, MAX_WBITS
25
27
 
26
28
  from argtools import argcount
29
+ from brotli import decompress as decompress_br # type: ignore
27
30
  from filewrap import bio_skip_iter, SupportsRead, SupportsWrite
28
31
  from http_response import get_filename, get_length, is_chunked, is_range_request
32
+ from zstandard import decompress as decompress_zstd
29
33
 
30
34
 
31
35
  if "__del__" not in HTTPResponse.__dict__:
32
36
  setattr(HTTPResponse, "__del__", HTTPResponse.close)
37
+ if "__del__" not in OpenerDirector.__dict__:
38
+ setattr(OpenerDirector, "__del__", OpenerDirector.close)
33
39
 
34
40
  CRE_search_charset = re_compile(r"\bcharset=(?P<charset>[^ ;]+)").search
35
41
 
36
42
 
43
+ def decompress_deflate(data, compresslevel=9):
44
+ # Fork from: https://stackoverflow.com/questions/1089662/python-inflate-and-deflate-implementations#answer-1089787
45
+ compress = compressobj(
46
+ compresslevel, # level: 0-9
47
+ DEFLATED, # method: must be DEFLATED
48
+ -MAX_WBITS, # window size in bits:
49
+ # -15..-8: negate, suppress header
50
+ # 8..15: normal
51
+ # 16..30: subtract 16, gzip header
52
+ DEF_MEM_LEVEL, # mem level: 1..8/9
53
+ 0 # strategy:
54
+ # 0 = Z_DEFAULT_STRATEGY
55
+ # 1 = Z_FILTERED
56
+ # 2 = Z_HUFFMAN_ONLY
57
+ # 3 = Z_RLE
58
+ # 4 = Z_FIXED
59
+ )
60
+ deflated = compress.compress(data)
61
+ deflated += compress.flush()
62
+ return deflated
63
+
64
+
65
+ def decompress_response(resp: HTTPResponse) -> bytes:
66
+ data = resp.read()
67
+ content_encoding = resp.headers.get("Content-Encoding")
68
+ match content_encoding:
69
+ case "gzip":
70
+ data = decompress_gzip(data)
71
+ case "deflate":
72
+ data = decompress_deflate(data)
73
+ case "br":
74
+ data = decompress_br(data)
75
+ case "zstd":
76
+ data = decompress_zstd(data)
77
+ return data
78
+
79
+
37
80
  def urlopen(
38
81
  url: str | Request,
39
82
  method: str = "GET",
@@ -44,7 +87,7 @@ def urlopen(
44
87
  timeout: None | int | float = None,
45
88
  cookies: None | CookieJar = None,
46
89
  proxy: None | tuple[str, str] = None,
47
- opener: OpenerDirector = build_opener(HTTPSHandler(context=_create_unverified_context())),
90
+ opener: None | OpenerDirector = None,
48
91
  context: None | SSLContext = None,
49
92
  origin: None | str = None,
50
93
  ) -> HTTPResponse:
@@ -95,7 +138,7 @@ def urlopen(
95
138
  if proxy:
96
139
  req.set_proxy(*proxy)
97
140
  if opener is None:
98
- opener = build_opener()
141
+ opener = build_opener(HTTPSHandler(context=_create_unverified_context()))
99
142
  if context is not None:
100
143
  opener.add_handler(HTTPSHandler(context=context))
101
144
  if cookies is not None:
@@ -136,17 +179,16 @@ def request(
136
179
  if parse is None:
137
180
  return resp
138
181
  with resp:
139
- if parse is False:
140
- return resp.read()
141
- elif parse is True:
142
- data = resp.read()
143
- content_type = resp.headers.get("Content-Type", "")
144
- if content_type == "application/json":
145
- return loads(data)
146
- elif content_type.startswith("application/json;"):
147
- return loads(data.decode(get_charset(content_type)))
148
- elif content_type.startswith("text/"):
149
- return data.decode(get_charset(content_type))
182
+ if isinstance(parse, bool):
183
+ data = decompress_response(resp)
184
+ if parse:
185
+ content_type = resp.headers.get("Content-Type", "")
186
+ if content_type == "application/json":
187
+ return loads(data)
188
+ elif content_type.startswith("application/json;"):
189
+ return loads(data.decode(get_charset(content_type)))
190
+ elif content_type.startswith("text/"):
191
+ return data.decode(get_charset(content_type))
150
192
  return data
151
193
  else:
152
194
  ac = argcount(parse)
@@ -154,7 +196,7 @@ def request(
154
196
  if ac == 1:
155
197
  return parse(resp)
156
198
  else:
157
- return parse(resp, resp.read())
199
+ return parse(resp, decompress_response(resp))
158
200
 
159
201
 
160
202
  def download(
File without changes