aioqzone 1.8.2.dev1__tar.gz → 1.8.3.dev1__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.
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/PKG-INFO +3 -3
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/README.md +1 -1
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/pyproject.toml +4 -4
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/__init__.py +2 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/profile.py +1 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/__init__.py +1 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/config.py +2 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/regex.py +1 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/retry.py +1 -2
- aioqzone-1.8.3.dev1/src/qqqr/__init__.py +4 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/qr/__init__.py +57 -11
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/capsess.py +1 -2
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/web.py +10 -5
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/iter.py +4 -4
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/jsjson.py +11 -7
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/net.py +2 -2
- aioqzone-1.8.2.dev1/src/qqqr/__init__.py +0 -4
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/LICENSE +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/h5/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/h5/model.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/login/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/login/_base.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/exception.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/message.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/feed.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/request.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/response.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/entity.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/entity.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/base.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/constant.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/exception.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/message.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/py.typed +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/qr/type.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/type.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/_model.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/_model.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/pil_utils.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/select/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/select/_types.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/slide/__init__.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/slide/_types.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/encrypt.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/h5.py +0 -0
- {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/encrypt.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aioqzone
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.3.dev1
|
|
4
4
|
Summary: A Python wrapper for Qzone login and H5 APIs.
|
|
5
5
|
Home-page: https://github.com/aioqzone/aioqzone
|
|
6
6
|
License: AGPL-3.0
|
|
@@ -23,7 +23,7 @@ Provides-Extra: slide-captcha
|
|
|
23
23
|
Requires-Dist: aiohttp (>=3.8.5,<4.0.0)
|
|
24
24
|
Requires-Dist: exceptiongroup (>=1.1.1,<2.0.0)
|
|
25
25
|
Requires-Dist: pillow (>=10.0.1,<11.0.0)
|
|
26
|
-
Requires-Dist: pychaosvm (>=0.3.4,<0.
|
|
26
|
+
Requires-Dist: pychaosvm (>=0.3.4,<0.5.0)
|
|
27
27
|
Requires-Dist: pydantic (>=2.0.3,<3.0.0)
|
|
28
28
|
Requires-Dist: pydantic-settings (>=2.0.2,<3.0.0)
|
|
29
29
|
Requires-Dist: rsa (>=4.8,<5.0)
|
|
@@ -43,7 +43,7 @@ aioqzone封装了一些Qzone接口。
|
|
|
43
43
|
[][home]
|
|
44
44
|
[][pypi]
|
|
45
45
|
[](https://github.com/psf/black)
|
|
46
|
-
[](https://t.me/
|
|
46
|
+
[](https://t.me/aioqzone_chatroom)
|
|
47
47
|
|
|
48
48
|
[English](README_en.md) | 简体中文
|
|
49
49
|
|
|
@@ -5,7 +5,7 @@ aioqzone封装了一些Qzone接口。
|
|
|
5
5
|
[][home]
|
|
6
6
|
[][pypi]
|
|
7
7
|
[](https://github.com/psf/black)
|
|
8
|
-
[](https://t.me/
|
|
8
|
+
[](https://t.me/aioqzone_chatroom)
|
|
9
9
|
|
|
10
10
|
[English](README_en.md) | 简体中文
|
|
11
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "aioqzone"
|
|
3
|
-
version = "1.8.
|
|
3
|
+
version = "1.8.3.dev1"
|
|
4
4
|
description = "A Python wrapper for Qzone login and H5 APIs."
|
|
5
5
|
authors = ["aioqzone <zzzzss990315@gmail.com>"]
|
|
6
6
|
license = "AGPL-3.0"
|
|
@@ -36,7 +36,7 @@ tenacity = "^8.2.3"
|
|
|
36
36
|
exceptiongroup = "^1.1.1"
|
|
37
37
|
tylisten = "^2.1.3"
|
|
38
38
|
pillow = "^10.0.1"
|
|
39
|
-
pychaosvm = { version = "
|
|
39
|
+
pychaosvm = { version = ">=0.3.4,<0.5.0", source = "aioqzone-index" }
|
|
40
40
|
slide-tc = {version = "~0.1.1", optional = true, source = "aioqzone-index" }
|
|
41
41
|
|
|
42
42
|
[tool.poetry.extras]
|
|
@@ -47,8 +47,8 @@ slide-captcha = ["slide-tc"]
|
|
|
47
47
|
optional = false
|
|
48
48
|
|
|
49
49
|
[tool.poetry.group.test.dependencies]
|
|
50
|
-
pytest = "^
|
|
51
|
-
pytest-asyncio = "~0.21.
|
|
50
|
+
pytest = "^8.2.0"
|
|
51
|
+
pytest-asyncio = "~0.21.2"
|
|
52
52
|
|
|
53
53
|
[tool.poetry.group.dev]
|
|
54
54
|
optional = true
|
|
@@ -12,6 +12,8 @@ TyHttpMethod = t.Union[t.Literal["GET"], t.Literal["POST"]]
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class QzoneApi(BaseModel, t.Generic[TyRequest, TyResponse]):
|
|
15
|
+
"""The base class for all Qzone APIs below."""
|
|
16
|
+
|
|
15
17
|
host: t.ClassVar[str] = "https://h5.qzone.qq.com"
|
|
16
18
|
http_method: t.ClassVar[TyHttpMethod]
|
|
17
19
|
path: t.ClassVar[str]
|
|
@@ -14,8 +14,7 @@ class RetryIfCode(retry_if_exception, Generic[_E]):
|
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
16
|
@abstractmethod
|
|
17
|
-
def get_code(cls, exc: _E) -> int:
|
|
18
|
-
...
|
|
17
|
+
def get_code(cls, exc: _E) -> int: ...
|
|
19
18
|
|
|
20
19
|
def __init__(self, *code: int) -> None:
|
|
21
20
|
super().__init__(lambda exc: isinstance(exc, self._exc_cls) and self.get_code(exc) in code)
|
|
@@ -28,13 +28,17 @@ PTLOGIN2 = URL("https://ptlogin2.qq.com")
|
|
|
28
28
|
|
|
29
29
|
@dataclass(unsafe_hash=True)
|
|
30
30
|
class QR:
|
|
31
|
+
"""Class :class:`QR` represents a QR code."""
|
|
32
|
+
|
|
31
33
|
png: t.Optional[bytes]
|
|
32
|
-
"""If None, the QR is pushed to user's client."""
|
|
34
|
+
"""QR code content. If None, the QR is pushed to user's client."""
|
|
33
35
|
sig: str
|
|
34
36
|
expired: bool = False
|
|
37
|
+
"""Whether the QR code is expired."""
|
|
35
38
|
|
|
36
39
|
@property
|
|
37
40
|
def pushed(self):
|
|
41
|
+
"""Whether the QR code is pushed to user's client."""
|
|
38
42
|
return self.png is None
|
|
39
43
|
|
|
40
44
|
|
|
@@ -49,9 +53,12 @@ class QrSession(LoginSession):
|
|
|
49
53
|
) -> None:
|
|
50
54
|
super().__init__(login_sig=login_sig, create_time=create_time)
|
|
51
55
|
self.refreshed = refresh_times
|
|
56
|
+
"""QR code refresh times counter."""
|
|
52
57
|
self.current_qr = first_qr
|
|
58
|
+
"""A :class:`QrSession` keeps a :class:`QR` object as current QR code."""
|
|
53
59
|
|
|
54
60
|
def new_qr(self, qr: QR):
|
|
61
|
+
"""Add a new QR code to this session."""
|
|
55
62
|
self.current_qr.expired = True
|
|
56
63
|
self.current_qr = qr
|
|
57
64
|
self.refreshed += 1
|
|
@@ -61,14 +68,36 @@ class _QrHookMixin:
|
|
|
61
68
|
def __init__(self, *args, **kwds) -> None:
|
|
62
69
|
super().__init__(*args, **kwds)
|
|
63
70
|
self.qr_fetched = MT.qr_fetched.with_timeout(60)
|
|
71
|
+
"""This emitter is triggered when a QR code is fetched."""
|
|
64
72
|
self.qr_cancelled = MT.qr_cancelled()
|
|
73
|
+
"""This emitter is triggered when QR login is cancelled."""
|
|
65
74
|
self.cancel = asyncio.Event()
|
|
75
|
+
"""Async-event indicating whether the loop should cancel the QR login."""
|
|
66
76
|
self.refresh = asyncio.Event()
|
|
77
|
+
"""Async-event indicating whether the loop should refresh the QR code immediately."""
|
|
67
78
|
|
|
68
79
|
|
|
69
80
|
class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
70
|
-
async def new(self) -> QrSession:
|
|
81
|
+
async def new(self, no_push=False) -> QrSession:
|
|
82
|
+
"""Create a :class:`QrSession`. This method will:
|
|
83
|
+
|
|
84
|
+
1. GET ``xlogin`` url to get ``pt_login_sig`` cookie;
|
|
85
|
+
|
|
86
|
+
#. Try "quick login" (the QR code is pushed to user's client);
|
|
87
|
+
|
|
88
|
+
#. Whether the QR code is pushed or not, a :class:`QR` object is created
|
|
89
|
+
and is hold by the returned :class:`QrSession`.
|
|
90
|
+
|
|
91
|
+
:param no_push: Do not try to push the QR code to user's client.
|
|
92
|
+
:return: a :class:`QrSession`
|
|
93
|
+
|
|
94
|
+
.. versionchanged:: 1.8.3
|
|
95
|
+
|
|
96
|
+
Added :obj:`no_push` param.
|
|
97
|
+
"""
|
|
71
98
|
login_sig = await self._pt_login_sig()
|
|
99
|
+
if no_push:
|
|
100
|
+
return QrSession(await self.show(), login_sig=login_sig)
|
|
72
101
|
|
|
73
102
|
cookie = self.client.cookie_jar.filter_cookies(PTLOGIN2).get("pt_guid_sig")
|
|
74
103
|
push_qr = False
|
|
@@ -88,6 +117,11 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
88
117
|
return QrSession(await self.show(push_qr), login_sig=login_sig)
|
|
89
118
|
|
|
90
119
|
async def show(self, push_qr=False) -> QR:
|
|
120
|
+
"""This method will call ``ptqrshow`` api and wrap the response QR bytes into :class:`QR`.
|
|
121
|
+
|
|
122
|
+
:param push_qr: push QR to mobile client.
|
|
123
|
+
:return: a :class:`QR` object.
|
|
124
|
+
"""
|
|
91
125
|
data = {
|
|
92
126
|
"appid": self.app.appid,
|
|
93
127
|
"daid": self.app.daid,
|
|
@@ -125,7 +159,7 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
125
159
|
async def poll(self, sess: QrSession) -> PollResp:
|
|
126
160
|
"""Poll QR status.
|
|
127
161
|
|
|
128
|
-
:raise `
|
|
162
|
+
:raise `aiohttp.ClientResponseError`: if response status code != 200
|
|
129
163
|
|
|
130
164
|
:return: a poll response object
|
|
131
165
|
"""
|
|
@@ -166,26 +200,37 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
166
200
|
*,
|
|
167
201
|
refresh_times: int = 6,
|
|
168
202
|
poll_freq: float = 3,
|
|
203
|
+
no_push=False,
|
|
169
204
|
):
|
|
170
|
-
"""Loop until cookie is returned or max
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
- If
|
|
205
|
+
"""Loop until cookie is returned or max :obj:`refresh_times` exceeds.
|
|
206
|
+
|
|
207
|
+
- This method will emit :obj:`.qr_fetched` event if a new qrcode is fetched.
|
|
208
|
+
|
|
209
|
+
- If the QR code is not scanned after :obj:`refresh_times`,
|
|
210
|
+
it will raise :exc:`~qqqr.exception.UserTimeout`.
|
|
211
|
+
|
|
212
|
+
- If :obj:`.refresh` is set, it will refresh qrcode at once without increasing expire counter.
|
|
213
|
+
|
|
214
|
+
- If :obj:`.cancel` is set, it will raise :exc:`~qqqr.exception.UserBreak` before next polling.
|
|
175
215
|
|
|
176
216
|
:meta public:
|
|
177
217
|
:param refresh_times: max qr expire times.
|
|
178
218
|
:param poll_freq: interval between two status polling, in seconds, default as 3.
|
|
219
|
+
:param no_push: Do not try to push the QR code to user's client.
|
|
220
|
+
|
|
221
|
+
:raise `UserTimeout`: if the QR code is not scanned after :obj:`refresh_times` expires.
|
|
222
|
+
:raise `UserBreak`: if :obj:`.cancel` is set.
|
|
223
|
+
|
|
224
|
+
.. versionchanged:: 1.8.3
|
|
179
225
|
|
|
180
|
-
|
|
181
|
-
:raise `UserBreak`: if :obj:`QrEvent.cancel_flag` is set.
|
|
226
|
+
Added :obj:`no_push` param.
|
|
182
227
|
"""
|
|
183
228
|
self.refresh.clear()
|
|
184
229
|
self.cancel.clear()
|
|
185
230
|
|
|
186
231
|
cnt_expire = 0
|
|
187
232
|
renew = False
|
|
188
|
-
sess = await self.new()
|
|
233
|
+
sess = await self.new(no_push)
|
|
189
234
|
|
|
190
235
|
while cnt_expire < refresh_times:
|
|
191
236
|
# BUG: should we wrap hook errors here?
|
|
@@ -202,6 +247,7 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
202
247
|
await asyncio.sleep(poll_freq)
|
|
203
248
|
stat = await self.poll(sess)
|
|
204
249
|
if stat.code == StatusCode.Expired:
|
|
250
|
+
sess.current_qr.expired = True
|
|
205
251
|
cnt_expire += 1
|
|
206
252
|
break
|
|
207
253
|
elif stat.code == StatusCode.Authenticated:
|
|
@@ -88,8 +88,7 @@ class BaseTcaptchaSession(ABC):
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
@abstractmethod
|
|
91
|
-
async def get_captcha_problem(self, client: ClientAdapter):
|
|
92
|
-
...
|
|
91
|
+
async def get_captcha_problem(self, client: ClientAdapter): ...
|
|
93
92
|
|
|
94
93
|
@abstractmethod
|
|
95
94
|
async def solve_captcha(self) -> str:
|
|
@@ -99,6 +99,7 @@ class _UpHookMixin:
|
|
|
99
99
|
def __init__(self, *args, **kwds) -> None:
|
|
100
100
|
super().__init__(*args, **kwds)
|
|
101
101
|
self.sms_code_input = MT.sms_code_input.with_timeout(60)
|
|
102
|
+
"""This emitter is triggered when SMS verify code is needed during login."""
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
@@ -132,17 +133,21 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
132
133
|
self.client, self.app.appid, str(self.login_page_url), fake_ip=fake_ip
|
|
133
134
|
)
|
|
134
135
|
|
|
135
|
-
async def new(self):
|
|
136
|
-
"""Create a :class:`UpWebSession`. This will
|
|
137
|
-
about whether this login needs a captcha, sms verification, etc.
|
|
136
|
+
async def new(self) -> UpWebSession:
|
|
137
|
+
"""Create a :class:`UpWebSession`. This will trigger a GET to ``xlogin`` url.
|
|
138
138
|
|
|
139
|
-
:raise `
|
|
139
|
+
:raise `aiohttp.ClientResponseError`: if response status != 200
|
|
140
140
|
|
|
141
141
|
:return: a up login session
|
|
142
142
|
"""
|
|
143
143
|
return UpWebSession(await self._pt_login_sig())
|
|
144
144
|
|
|
145
145
|
async def check(self, sess: UpWebSession):
|
|
146
|
+
"""This will call ``check`` api of Qzone, and receive result about
|
|
147
|
+
whether this login needs a captcha, sms verification, etc.
|
|
148
|
+
|
|
149
|
+
:param sess: Session got from :meth:`~UpWebLogin.new`.
|
|
150
|
+
"""
|
|
146
151
|
data = {
|
|
147
152
|
"regmaster": "",
|
|
148
153
|
"pt_tea": 2,
|
|
@@ -217,7 +222,7 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
217
222
|
data.update(const)
|
|
218
223
|
return data
|
|
219
224
|
|
|
220
|
-
async def try_login(self, sess: UpWebSession):
|
|
225
|
+
async def try_login(self, sess: UpWebSession) -> LoginResp:
|
|
221
226
|
"""
|
|
222
227
|
Check if current session meets the login condition.
|
|
223
228
|
It takes a session object and returns response of this try.
|
|
@@ -5,8 +5,9 @@ D = t.TypeVar("D")
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@t.overload
|
|
8
|
-
def first(
|
|
9
|
-
|
|
8
|
+
def first(
|
|
9
|
+
it: t.Iterable[T], pred: t.Optional[t.Callable[[T], t.Optional[object]]] = None
|
|
10
|
+
) -> T: ...
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@t.overload
|
|
@@ -15,8 +16,7 @@ def first(
|
|
|
15
16
|
pred: t.Optional[t.Callable[[T], t.Optional[object]]] = None,
|
|
16
17
|
*,
|
|
17
18
|
default: D,
|
|
18
|
-
) -> t.Union[T, D]:
|
|
19
|
-
...
|
|
19
|
+
) -> t.Union[T, D]: ...
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def first(
|
|
@@ -10,9 +10,13 @@ JsonValue = Union[bool, int, str, JsonDict, JsonList]
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class AstLoader:
|
|
13
|
-
"""
|
|
13
|
+
""":class:`AstLoader` uses standard :mod:`ast` module to parse the js/json"""
|
|
14
14
|
|
|
15
15
|
class RewriteUndef(ast.NodeTransformer):
|
|
16
|
+
"""
|
|
17
|
+
:meta private:
|
|
18
|
+
"""
|
|
19
|
+
|
|
16
20
|
const = {
|
|
17
21
|
"undefined": ast.Constant(value=None),
|
|
18
22
|
"null": ast.Constant(value=None),
|
|
@@ -28,12 +32,12 @@ class AstLoader:
|
|
|
28
32
|
@classmethod
|
|
29
33
|
def json_loads(cls, js: str, filename: str = "stdin") -> JsonValue:
|
|
30
34
|
"""
|
|
31
|
-
The json_loads function loads a JSON object from a js/json string. It uses standard
|
|
35
|
+
The :meth:`~AstLoader.json_loads` function loads a JSON object from a js/json string. It uses standard
|
|
32
36
|
:mod:`ast` module to parse the js/json.
|
|
33
37
|
|
|
34
38
|
:param js: Used to Pass the js/json string to be parsed.
|
|
35
39
|
:param filename: Used to Specify the name of the file that is being read. This is only for debug use.
|
|
36
|
-
:return: A
|
|
40
|
+
:return: A :obj:`JsonValue` object.
|
|
37
41
|
"""
|
|
38
42
|
js = dedent(js).replace(r"\/", "/")
|
|
39
43
|
node = ast.parse(js, mode="eval")
|
|
@@ -43,14 +47,14 @@ class AstLoader:
|
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
def json_loads(js: str) -> JsonValue:
|
|
46
|
-
"""The json_loads function converts a string representation of JS/JSON data into a Python object.
|
|
50
|
+
"""The :meth:`json_loads` function converts a string representation of JS/JSON data into a Python object.
|
|
47
51
|
Current implementation is using :external+python:mod:`ast`.
|
|
48
52
|
|
|
49
|
-
If you need more parameters or another implementation, call `xxxLoader.json_loads` instead.
|
|
50
|
-
|
|
51
53
|
.. seealso:: :meth:`.AstLoader.json_loads`
|
|
52
54
|
|
|
55
|
+
If you need more parameters or another implementation, call ``xxxLoader.json_loads`` instead.
|
|
56
|
+
|
|
53
57
|
:param js: Used to Pass the JS/JSON string.
|
|
54
|
-
:return: A
|
|
58
|
+
:return: A :obj:`JsonValue` object.
|
|
55
59
|
"""
|
|
56
60
|
return AstLoader.json_loads(js)
|
|
@@ -9,12 +9,12 @@ __all__ = ["raise_for_status", "get_all_cookie", "ClientAdapter"]
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def raise_for_status(response: Response, *accept_code: int):
|
|
12
|
-
"""A checker more strict than :meth:`~
|
|
12
|
+
"""A checker more strict than :meth:`~aiohttp.ClientResponse.raise_for_status`.
|
|
13
13
|
|
|
14
14
|
:param response: Client response to check.
|
|
15
15
|
:param accept_code: Overwrite codes that can be accepted, If not given, default is `(200, )`
|
|
16
16
|
|
|
17
|
-
:raise `
|
|
17
|
+
:raise `aiohttp.ClientResponseError`: if status not in :obj:`accept_code`
|
|
18
18
|
"""
|
|
19
19
|
response.raise_for_status
|
|
20
20
|
accept_code = accept_code or (200,)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|