python-http-helper 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
Potentially problematic release.
This version of python-http-helper might be problematic. Click here for more details.
- http_helper/__init__.py +11 -0
- http_helper/client/async_proxy.py +210 -0
- {utils → http_helper/utils}/log.py +1 -1
- {python_http_helper-0.1.0.dist-info → python_http_helper-0.2.1.dist-info}/METADATA +4 -3
- python_http_helper-0.2.1.dist-info/RECORD +12 -0
- python_http_helper-0.2.1.dist-info/top_level.txt +1 -0
- client/async_proxy.py +0 -109
- python_http_helper-0.1.0.dist-info/RECORD +0 -11
- python_http_helper-0.1.0.dist-info/top_level.txt +0 -2
- {client → http_helper/client}/__init__.py +0 -0
- {utils → http_helper/utils}/__init__.py +0 -0
- {utils → http_helper/utils}/http_execption.py +0 -0
- {utils → http_helper/utils}/reponse_handle_utils.py +0 -0
- {python_http_helper-0.1.0.dist-info → python_http_helper-0.2.1.dist-info}/WHEEL +0 -0
- {python_http_helper-0.1.0.dist-info → python_http_helper-0.2.1.dist-info}/licenses/LICENSE +0 -0
http_helper/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: http-helper
|
|
5
|
+
# FileName: __init__.py
|
|
6
|
+
# Description: http帮助包
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2025/11/24
|
|
9
|
+
# Copyright ©2011-2025. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: http-helper
|
|
5
|
+
# FileName: async_proxy.py
|
|
6
|
+
# Description: 客户端异步代理
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2025/11/24
|
|
9
|
+
# Copyright ©2011-2025. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
12
|
+
import re
|
|
13
|
+
import json
|
|
14
|
+
import aiohttp
|
|
15
|
+
import asyncio
|
|
16
|
+
from yarl import URL
|
|
17
|
+
from urllib.parse import quote
|
|
18
|
+
from ..utils.log import logger
|
|
19
|
+
from typing import Any, Dict, Optional, List
|
|
20
|
+
from ..utils.http_execption import HttpClientError
|
|
21
|
+
from ..utils.reponse_handle_utils import get_html_title
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HttpClientFactory:
|
|
25
|
+
__retry: int = 0
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
protocol: str = "https",
|
|
30
|
+
domain: str = "api.weixin.qq.com",
|
|
31
|
+
timeout: int = 10,
|
|
32
|
+
retry: int = 0,
|
|
33
|
+
enable_log: bool = False,
|
|
34
|
+
cookie_jar: Optional[aiohttp.CookieJar] = None,
|
|
35
|
+
playwright_state: Dict[str, Any] = None,
|
|
36
|
+
proxy_config: Optional[Dict[str, str]] = None
|
|
37
|
+
):
|
|
38
|
+
self.base_url = f"{protocol}://{domain}"
|
|
39
|
+
self.protocol = protocol
|
|
40
|
+
self.domain = domain
|
|
41
|
+
self.timeout = aiohttp.ClientTimeout(total=timeout)
|
|
42
|
+
self.__retry = retry
|
|
43
|
+
self.enable_log = enable_log
|
|
44
|
+
self.proxy_url = self.build_proxy_url(proxy_config)
|
|
45
|
+
|
|
46
|
+
# 初始化 session
|
|
47
|
+
self.session = aiohttp.ClientSession(timeout=self.timeout, cookie_jar=cookie_jar or aiohttp.CookieJar())
|
|
48
|
+
if playwright_state:
|
|
49
|
+
self._load_playwright_cookies_to_aiohttp(playwright_state)
|
|
50
|
+
self.valid_methods = {"get", "post", "put", "delete"}
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def build_proxy_url(proxy_config: Optional[Dict[str, str]]) -> Optional[str]:
|
|
54
|
+
"""
|
|
55
|
+
将 {server, username, password} 转为 aiohttp 可用的代理 URL
|
|
56
|
+
:param proxy_config: 格式如:
|
|
57
|
+
{
|
|
58
|
+
"server": "http://127.0.0.1:1234",
|
|
59
|
+
"username": "<USERNAME>",
|
|
60
|
+
"password": "<PASSWORD>",
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
if not proxy_config or not isinstance(proxy_config, dict) or not proxy_config.get("server"):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
server = proxy_config["server"].strip()
|
|
67
|
+
# 去掉协议头(兼容带或不带 http:// 的情况)
|
|
68
|
+
if server.startswith(("http://", "https://")):
|
|
69
|
+
host_port = server.split("://", 1)[1]
|
|
70
|
+
else:
|
|
71
|
+
host_port = server
|
|
72
|
+
|
|
73
|
+
if proxy_config.get("username") and proxy_config.get("password"):
|
|
74
|
+
# URL 编码用户名和密码(防止 @ : / 等特殊字符破坏 URL)
|
|
75
|
+
username = quote(proxy_config["username"], safe="")
|
|
76
|
+
password = quote(proxy_config["password"], safe="")
|
|
77
|
+
url = f"http://{username}:{password}@{host_port}"
|
|
78
|
+
elif not proxy_config.get("username") and not proxy_config.get("password"):
|
|
79
|
+
url = f"http://{host_port}"
|
|
80
|
+
else:
|
|
81
|
+
raise HttpClientError("代理参数缺失 username 或 password")
|
|
82
|
+
return url
|
|
83
|
+
|
|
84
|
+
def _load_playwright_cookies_to_aiohttp(self, playwright_state: Dict[str, Any]):
|
|
85
|
+
"""将 Playwright storage_state 中的 cookies 加载到 aiohttp session"""
|
|
86
|
+
for ck in playwright_state.get("cookies", []):
|
|
87
|
+
name = ck["name"]
|
|
88
|
+
value = ck["value"]
|
|
89
|
+
domain = ck["domain"]
|
|
90
|
+
path = ck.get("path", "/")
|
|
91
|
+
|
|
92
|
+
# 构造合法 URL 用于设置 cookie(主机名不能以 . 开头)
|
|
93
|
+
host = domain.lstrip(".") if domain.startswith(".") else domain
|
|
94
|
+
url = URL(f"{self.protocol}://{host}{path}")
|
|
95
|
+
|
|
96
|
+
# 设置 cookie
|
|
97
|
+
self.session.cookie_jar.update_cookies(cookies={name: value}, response_url=url)
|
|
98
|
+
|
|
99
|
+
async def request(
|
|
100
|
+
self,
|
|
101
|
+
method: str,
|
|
102
|
+
url: str,
|
|
103
|
+
*,
|
|
104
|
+
params: Dict[str, Any] = None,
|
|
105
|
+
json_data: Any = None,
|
|
106
|
+
data: Any = None,
|
|
107
|
+
headers: Dict[str, str] = None,
|
|
108
|
+
is_end: bool = True,
|
|
109
|
+
has_cookie: bool = False,
|
|
110
|
+
proxy_config: Optional[Dict[str, str]] = None,
|
|
111
|
+
exception_keywords: Optional[List[str]] = None
|
|
112
|
+
) -> Any:
|
|
113
|
+
if proxy_config:
|
|
114
|
+
self.proxy_url = self.build_proxy_url(proxy_config)
|
|
115
|
+
method = method.lower().strip()
|
|
116
|
+
if method not in self.valid_methods:
|
|
117
|
+
raise HttpClientError(f"Invalid Request method: {method}")
|
|
118
|
+
|
|
119
|
+
full_url = f"{self.base_url}{url}"
|
|
120
|
+
|
|
121
|
+
# 重试机制
|
|
122
|
+
attempts = self.__retry + 1
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
for attempt in range(1, attempts + 1):
|
|
126
|
+
try:
|
|
127
|
+
if self.enable_log:
|
|
128
|
+
logger.debug(f"{method.upper()} Request {full_url} attempt {attempt}")
|
|
129
|
+
|
|
130
|
+
async with self.session.request(
|
|
131
|
+
method=method,
|
|
132
|
+
url=full_url,
|
|
133
|
+
proxy=self.proxy_url,
|
|
134
|
+
params=params or None,
|
|
135
|
+
json=json_data,
|
|
136
|
+
data=data,
|
|
137
|
+
headers=headers,
|
|
138
|
+
) as resp:
|
|
139
|
+
|
|
140
|
+
# 非 2xx 抛异常
|
|
141
|
+
if resp.status >= 400:
|
|
142
|
+
error_text = self.parse_error_text(
|
|
143
|
+
exception_keywords=exception_keywords, error_text=await resp.text()
|
|
144
|
+
)
|
|
145
|
+
raise HttpClientError(error_text)
|
|
146
|
+
|
|
147
|
+
# 检查响应的 Content-Type
|
|
148
|
+
content_type = resp.headers.get("Content-Type", "").lower()
|
|
149
|
+
|
|
150
|
+
# 尝试 JSON 解码
|
|
151
|
+
if "application/json" in content_type or "text/json" in content_type:
|
|
152
|
+
try:
|
|
153
|
+
json_data = await resp.json(content_type=None) # 忽略非法 content-type
|
|
154
|
+
except (Exception,):
|
|
155
|
+
text = await resp.text()
|
|
156
|
+
json_data = json.loads(text)
|
|
157
|
+
elif "text/html" in content_type:
|
|
158
|
+
# 纯文本类型
|
|
159
|
+
html = await resp.text()
|
|
160
|
+
json_data = {
|
|
161
|
+
"code": resp.status,
|
|
162
|
+
"message": get_html_title(html=html),
|
|
163
|
+
"data": html
|
|
164
|
+
}
|
|
165
|
+
else:
|
|
166
|
+
# 其他类型,默认视为二进制内容
|
|
167
|
+
# content = await resp.content.readany() # 只读当前缓冲区,可能只是部分数据,非阻塞、低级 API
|
|
168
|
+
content = await resp.read() # 完整响应体
|
|
169
|
+
try:
|
|
170
|
+
text = content.decode('utf-8')
|
|
171
|
+
except UnicodeDecodeError:
|
|
172
|
+
text = content.decode('latin1') # fallback
|
|
173
|
+
json_data = dict(code=resp.status, message=get_html_title(html=text), data=text)
|
|
174
|
+
if has_cookie is True:
|
|
175
|
+
# resp.headers 的类型是 CIMultiDict,getall() 在 key 不存在时会抛出 KeyError,因此需要给一个空list作为默认值
|
|
176
|
+
set_cookie_headers = resp.headers.getall("Set-Cookie", list())
|
|
177
|
+
json_data["cookies"] = set_cookie_headers[0] if len(
|
|
178
|
+
set_cookie_headers) == 1 else set_cookie_headers # 返回列表
|
|
179
|
+
return json_data
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
if attempt == attempts:
|
|
183
|
+
raise HttpClientError(f"Request failed after {attempts} attempts: {e}")
|
|
184
|
+
await asyncio.sleep(1 * attempt) # 递增式重试间隔
|
|
185
|
+
finally:
|
|
186
|
+
if is_end is True:
|
|
187
|
+
await self.close()
|
|
188
|
+
|
|
189
|
+
async def close(self):
|
|
190
|
+
if self.session and not self.session.closed:
|
|
191
|
+
await self.session.close()
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def parse_error_text(error_text: str, exception_keywords: Optional[List[str]] = None) -> str:
|
|
195
|
+
if not exception_keywords:
|
|
196
|
+
return error_text
|
|
197
|
+
_exception_keywords = [
|
|
198
|
+
r'<h3[^>]*class="font-bold"[^>]*>([^<]+)</h3>'
|
|
199
|
+
]
|
|
200
|
+
if exception_keywords:
|
|
201
|
+
_exception_keywords.extend(exception_keywords)
|
|
202
|
+
for exception_keyword in _exception_keywords:
|
|
203
|
+
match = re.search(exception_keyword, error_text)
|
|
204
|
+
if match:
|
|
205
|
+
error_text = match.group(1).strip()
|
|
206
|
+
break
|
|
207
|
+
# 尝试提取青岛航空的提示信息(可选增强)
|
|
208
|
+
if "您的IP由于频繁访问已受限" in error_text:
|
|
209
|
+
raise HttpClientError(f"IP blocked by QDAir: {error_text}")
|
|
210
|
+
return error_text
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_http_helper
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: http helper python package
|
|
5
5
|
Author-email: ckf10000 <ckf10000@sina.com>
|
|
6
6
|
License: Apache License
|
|
@@ -210,7 +210,8 @@ Project-URL: Issues, https://github.com/ckf10000/http-helper/issues
|
|
|
210
210
|
Requires-Python: >=3.12
|
|
211
211
|
Description-Content-Type: text/markdown
|
|
212
212
|
License-File: LICENSE
|
|
213
|
-
Requires-Dist: aiohttp
|
|
213
|
+
Requires-Dist: aiohttp==3.13.2; python_version >= "3.12"
|
|
214
|
+
Requires-Dist: yarl==1.22.0; python_version >= "3.12"
|
|
214
215
|
Dynamic: license-file
|
|
215
216
|
|
|
216
217
|
# http-helper
|
|
@@ -230,7 +231,7 @@ Dynamic: license-file
|
|
|
230
231
|
- dist/python-http-helper-0.1.0.tar.gz
|
|
231
232
|
- dist/python-http-helper-0.1.0-py3-none-any.whl
|
|
232
233
|
- 2. 发布
|
|
233
|
-
- twine upload dist/*
|
|
234
|
+
- twine upload dist/* 或者 twine upload -r public dist/* 或者 twine upload --repository pypi dist/* --skip-existing --verbose --cert false 忽略证书
|
|
234
235
|
|
|
235
236
|
#### 使用说明
|
|
236
237
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
http_helper/__init__.py,sha256=RKb5JmhPJDEEuZ6XtBGm__z38vKVo6pZ-QOuudZSnvg,470
|
|
2
|
+
http_helper/client/__init__.py,sha256=rCu_vOiNkNWNQtiQLoeGrcIHTK-u-9C_NeQlXd5fo9U,469
|
|
3
|
+
http_helper/client/async_proxy.py,sha256=Skli2iBAZDRMh3d13KCDFLeLNpuXy0UWYDAfv9Xkdds,9392
|
|
4
|
+
http_helper/utils/__init__.py,sha256=2U-xO-3A3SnCawQMzip3LuoNjH0SiZcCFB2bWeacKmU,466
|
|
5
|
+
http_helper/utils/http_execption.py,sha256=w-yRbvayrE9kR9PZsFtEWmRXSoAIo-k4gS_cVRDg5O8,560
|
|
6
|
+
http_helper/utils/log.py,sha256=ryuQIhu-H1FR5dSrNZaMx91IfH4sGA5K-xFZTUowz68,518
|
|
7
|
+
http_helper/utils/reponse_handle_utils.py,sha256=6tFf3jmgjjzHt_EAmMKXITosq4-_5O7iSvkF3AREqus,805
|
|
8
|
+
python_http_helper-0.2.1.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
9
|
+
python_http_helper-0.2.1.dist-info/METADATA,sha256=agPo5wVQGvJ3Zsw3MSUZhzF73aRwHpkmIbhqmR5ifB0,14334
|
|
10
|
+
python_http_helper-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
python_http_helper-0.2.1.dist-info/top_level.txt,sha256=3gy3yvKZaKUW4_LRfqknrRwF9MOPkXD7jHkM-Q-BhC4,12
|
|
12
|
+
python_http_helper-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
http_helper
|
client/async_proxy.py
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
-
# ProjectName: http-helper
|
|
5
|
-
# FileName: async_proxy.py
|
|
6
|
-
# Description: 客户端异步代理
|
|
7
|
-
# Author: ASUS
|
|
8
|
-
# CreateDate: 2025/11/24
|
|
9
|
-
# Copyright ©2011-2025. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
-
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
-
"""
|
|
12
|
-
import json
|
|
13
|
-
import aiohttp
|
|
14
|
-
import asyncio
|
|
15
|
-
from typing import Any, Dict
|
|
16
|
-
from ..utils.log import logger
|
|
17
|
-
from ..utils.http_execption import HttpClientError
|
|
18
|
-
from ..utils.reponse_handle_utils import get_html_title
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class HttpClientFactory:
|
|
22
|
-
__retry: int = 0
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
protocol: str = "https",
|
|
27
|
-
domain: str = "api.weixin.qq.com",
|
|
28
|
-
timeout: int = 10,
|
|
29
|
-
retry: int = 0,
|
|
30
|
-
enable_log: bool = False
|
|
31
|
-
):
|
|
32
|
-
self.base_url = "://".join([protocol, domain])
|
|
33
|
-
self.timeout = aiohttp.ClientTimeout(total=timeout)
|
|
34
|
-
self.__retry = retry
|
|
35
|
-
self.enable_log = enable_log
|
|
36
|
-
self.session = aiohttp.ClientSession(timeout=self.timeout)
|
|
37
|
-
|
|
38
|
-
self.valid_methods = {"get", "post", "put", "delete"}
|
|
39
|
-
|
|
40
|
-
async def request(
|
|
41
|
-
self,
|
|
42
|
-
method: str,
|
|
43
|
-
url: str,
|
|
44
|
-
*,
|
|
45
|
-
params: Dict[str, Any] = None,
|
|
46
|
-
json_data: Any = None,
|
|
47
|
-
data: Any = None,
|
|
48
|
-
headers: Dict[str, str] = None,
|
|
49
|
-
is_end: bool = True
|
|
50
|
-
) -> Any:
|
|
51
|
-
|
|
52
|
-
method = method.lower().strip()
|
|
53
|
-
if method not in self.valid_methods:
|
|
54
|
-
raise HttpClientError(f"Invalid Request method: {method}")
|
|
55
|
-
|
|
56
|
-
full_url = f"{self.base_url}{url}"
|
|
57
|
-
|
|
58
|
-
# 重试机制
|
|
59
|
-
attempts = self.__retry + 1
|
|
60
|
-
for attempt in range(1, attempts + 1):
|
|
61
|
-
try:
|
|
62
|
-
if self.enable_log:
|
|
63
|
-
logger.debug(f"{method.upper()} Request {full_url} attempt {attempt}")
|
|
64
|
-
|
|
65
|
-
async with self.session.request(
|
|
66
|
-
method=method,
|
|
67
|
-
url=full_url,
|
|
68
|
-
params=params or None,
|
|
69
|
-
json=json_data,
|
|
70
|
-
data=data,
|
|
71
|
-
headers=headers,
|
|
72
|
-
) as resp:
|
|
73
|
-
|
|
74
|
-
# 非 2xx 抛异常
|
|
75
|
-
if resp.status >= 400:
|
|
76
|
-
raise HttpClientError(f"Response status {resp.status} Error: {await resp.text()}")
|
|
77
|
-
|
|
78
|
-
# 检查响应的 Content-Type
|
|
79
|
-
content_type = resp.headers.get("Content-Type", "")
|
|
80
|
-
|
|
81
|
-
# 尝试 JSON 解码
|
|
82
|
-
try:
|
|
83
|
-
# 如果 Content-Type 是 text/json,手动解析 JSON
|
|
84
|
-
if "text/json" in content_type:
|
|
85
|
-
json_data = await resp.text() # 获取响应文本
|
|
86
|
-
# 手动解析 JSON
|
|
87
|
-
json_data = json.loads(json_data)
|
|
88
|
-
elif "application/json" in content_type:
|
|
89
|
-
json_data = await resp.json()
|
|
90
|
-
elif "text/html" in content_type:
|
|
91
|
-
# 纯文本类型
|
|
92
|
-
json_data = dict(code=resp.status, message=get_html_title(html=await resp.text()),
|
|
93
|
-
data=await resp.text())
|
|
94
|
-
else:
|
|
95
|
-
# 其他类型,默认视为二进制内容
|
|
96
|
-
content = await resp.content.readany()
|
|
97
|
-
content = content.decode('utf-8')
|
|
98
|
-
json_data = dict(code=resp.status, message=get_html_title(html=content), data=content)
|
|
99
|
-
return json_data
|
|
100
|
-
except aiohttp.ContentTypeError as e:
|
|
101
|
-
raise HttpClientError(f"Response parse error: {e}")
|
|
102
|
-
|
|
103
|
-
except Exception as e:
|
|
104
|
-
if attempt == attempts:
|
|
105
|
-
raise HttpClientError(f"Request failed after {attempts} attempts: {e}")
|
|
106
|
-
await asyncio.sleep(1 * attempt) # 递增式重试间隔
|
|
107
|
-
|
|
108
|
-
if is_end is True:
|
|
109
|
-
await self.session.close()
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
client/__init__.py,sha256=rCu_vOiNkNWNQtiQLoeGrcIHTK-u-9C_NeQlXd5fo9U,469
|
|
2
|
-
client/async_proxy.py,sha256=2gZ6Ak5erNSA2VRVP0Nyuf1K5gWq5roTGtEkoaG7dD0,4388
|
|
3
|
-
python_http_helper-0.1.0.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
4
|
-
utils/__init__.py,sha256=2U-xO-3A3SnCawQMzip3LuoNjH0SiZcCFB2bWeacKmU,466
|
|
5
|
-
utils/http_execption.py,sha256=w-yRbvayrE9kR9PZsFtEWmRXSoAIo-k4gS_cVRDg5O8,560
|
|
6
|
-
utils/log.py,sha256=lm9oRYTkYdTsXe-blicDlx-ZRikhtlGs_oM-86wF52s,512
|
|
7
|
-
utils/reponse_handle_utils.py,sha256=6tFf3jmgjjzHt_EAmMKXITosq4-_5O7iSvkF3AREqus,805
|
|
8
|
-
python_http_helper-0.1.0.dist-info/METADATA,sha256=_tITgIzxQz-2cx-4RaS-unf4jhBOraJnjT6WmXgFaFM,14144
|
|
9
|
-
python_http_helper-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
python_http_helper-0.1.0.dist-info/top_level.txt,sha256=Y3jknC7ZyId_T4_pOtSk7-pTuXZRm9irp1ATqBVjhLY,13
|
|
11
|
-
python_http_helper-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|