pixelarraythirdparty 1.1.9__tar.gz → 1.2.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.
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/PKG-INFO +1 -1
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/__init__.py +1 -1
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/client.py +28 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/feedback/feedback.py +8 -1
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/order/order.py +33 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/unified_login/__init__.py +2 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/unified_login/unified_login.py +423 -106
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/user/user.py +0 -3
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/PKG-INFO +1 -1
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pyproject.toml +1 -1
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/LICENSE +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/MANIFEST.in +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/cron/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/cron/cron.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/feedback/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/order/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/product/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/product/product.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/project/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/project/project.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/user/__init__.py +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/SOURCES.txt +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/dependency_links.txt +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/requires.txt +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/top_level.txt +0 -0
- {pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/setup.cfg +0 -0
|
@@ -4,6 +4,12 @@ from typing import Dict, Any, Tuple
|
|
|
4
4
|
|
|
5
5
|
class AsyncClient:
|
|
6
6
|
def __init__(self, api_key: str):
|
|
7
|
+
"""
|
|
8
|
+
description:
|
|
9
|
+
初始化异步客户端
|
|
10
|
+
parameters:
|
|
11
|
+
api_key(str): API密钥,用于身份验证
|
|
12
|
+
"""
|
|
7
13
|
self.base_url = "https://thirdparty.pixelarrayai.com"
|
|
8
14
|
self.api_key = api_key
|
|
9
15
|
self.headers = {
|
|
@@ -14,6 +20,17 @@ class AsyncClient:
|
|
|
14
20
|
async def _request(
|
|
15
21
|
self, method: str, url: str, **kwargs
|
|
16
22
|
) -> Tuple[Dict[str, Any], bool]:
|
|
23
|
+
"""
|
|
24
|
+
description:
|
|
25
|
+
发送异步HTTP请求的通用方法
|
|
26
|
+
parameters:
|
|
27
|
+
method(str): HTTP方法,如"GET"、"POST"、"PUT"、"DELETE"等
|
|
28
|
+
url(str): 请求URL路径(相对于base_url)
|
|
29
|
+
**kwargs: 其他请求参数,如json、params、headers等
|
|
30
|
+
return:
|
|
31
|
+
data(Dict[str, Any]): 响应数据字典,如果请求失败则返回空字典
|
|
32
|
+
success(bool): 请求是否成功
|
|
33
|
+
"""
|
|
17
34
|
# 如果kwargs中有headers,则合并headers
|
|
18
35
|
headers = self.headers.copy()
|
|
19
36
|
if "headers" in kwargs:
|
|
@@ -38,6 +55,17 @@ class AsyncClient:
|
|
|
38
55
|
async def _request_raw(
|
|
39
56
|
self, method: str, url: str, **kwargs
|
|
40
57
|
) -> Tuple[int, Dict[str, Any]]:
|
|
58
|
+
"""
|
|
59
|
+
description:
|
|
60
|
+
发送异步HTTP请求的原始方法,返回完整的HTTP状态码和响应数据
|
|
61
|
+
parameters:
|
|
62
|
+
method(str): HTTP方法,如"GET"、"POST"、"PUT"、"DELETE"等
|
|
63
|
+
url(str): 请求URL路径(相对于base_url)
|
|
64
|
+
**kwargs: 其他请求参数,如json、params、headers等
|
|
65
|
+
return:
|
|
66
|
+
status_code(int): HTTP状态码
|
|
67
|
+
data(Dict[str, Any]): 响应数据字典,如果解析失败则返回空字典
|
|
68
|
+
"""
|
|
41
69
|
headers = self.headers.copy()
|
|
42
70
|
if "headers" in kwargs:
|
|
43
71
|
headers.update(kwargs["headers"])
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/feedback/feedback.py
RENAMED
|
@@ -68,7 +68,14 @@ class FeedbackManagerAsync(AsyncClient):
|
|
|
68
68
|
file_type: str,
|
|
69
69
|
):
|
|
70
70
|
"""
|
|
71
|
-
|
|
71
|
+
description:
|
|
72
|
+
内部方法:上传文件到OSS
|
|
73
|
+
parameters:
|
|
74
|
+
feedback_id(int): 反馈ID
|
|
75
|
+
file_data(Union[str, BinaryIO, bytes]): 文件数据,可以是文件路径、文件对象或字节数据
|
|
76
|
+
file_type(str): 文件类型,可选值:"image"(图片)、"video"(视频)
|
|
77
|
+
return:
|
|
78
|
+
success(bool): 上传是否成功
|
|
72
79
|
"""
|
|
73
80
|
# 读取文件数据
|
|
74
81
|
file_content = None
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/order/order.py
RENAMED
|
@@ -43,6 +43,39 @@ class OrderManagerAsync(AsyncClient):
|
|
|
43
43
|
return {}, False
|
|
44
44
|
return data, True
|
|
45
45
|
|
|
46
|
+
async def create_order_v2(
|
|
47
|
+
self,
|
|
48
|
+
product_id: str,
|
|
49
|
+
body: str = None,
|
|
50
|
+
remark: str = None,
|
|
51
|
+
payment_channel: str = "WECHAT",
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
description:
|
|
55
|
+
创建订单并直接返回支付二维码(V2,推荐)。
|
|
56
|
+
相比旧流程(create_order + generate_qr_code),该接口一次请求完成下单和二维码链接生成。
|
|
57
|
+
parameters:
|
|
58
|
+
product_id(str): 产品ID
|
|
59
|
+
body(str): 商品描述
|
|
60
|
+
remark(str): 订单备注
|
|
61
|
+
payment_channel(str): 支付渠道,可选值:"WECHAT"、"ALIPAY"、"PAYPAL"
|
|
62
|
+
return:
|
|
63
|
+
data(dict): 返回数据
|
|
64
|
+
- order(dict): 订单信息(字段同 create_order 返回)
|
|
65
|
+
- qr_code_url(str): 支付二维码图片URL
|
|
66
|
+
success(bool): 操作是否成功
|
|
67
|
+
"""
|
|
68
|
+
data = {
|
|
69
|
+
"product_id": product_id,
|
|
70
|
+
"body": body,
|
|
71
|
+
"remark": remark,
|
|
72
|
+
"payment_channel": payment_channel,
|
|
73
|
+
}
|
|
74
|
+
data, success = await self._request("POST", "/api/orders/create_v2", json=data)
|
|
75
|
+
if not success:
|
|
76
|
+
return {}, False
|
|
77
|
+
return data, True
|
|
78
|
+
|
|
46
79
|
async def list_order(
|
|
47
80
|
self,
|
|
48
81
|
page: int = 1,
|
|
@@ -6,6 +6,244 @@ from typing import Dict, Optional, Tuple
|
|
|
6
6
|
from pixelarraythirdparty.client import AsyncClient
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class OAuth2Login(AsyncClient):
|
|
10
|
+
"""
|
|
11
|
+
统一的 OAuth2 登录客户端基类
|
|
12
|
+
|
|
13
|
+
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音等)
|
|
14
|
+
通过配置不同的端点来支持不同的提供商
|
|
15
|
+
|
|
16
|
+
使用示例:
|
|
17
|
+
```
|
|
18
|
+
# Google登录
|
|
19
|
+
google = OAuth2Login(
|
|
20
|
+
api_key="your_api_key",
|
|
21
|
+
provider="google"
|
|
22
|
+
)
|
|
23
|
+
user_info, success = await google.login()
|
|
24
|
+
|
|
25
|
+
# 微信PC端扫码登录
|
|
26
|
+
wechat = OAuth2Login(
|
|
27
|
+
api_key="your_api_key",
|
|
28
|
+
provider="wechat",
|
|
29
|
+
login_type="desktop"
|
|
30
|
+
)
|
|
31
|
+
user_info, success = await wechat.login()
|
|
32
|
+
|
|
33
|
+
# 微信手机端登录
|
|
34
|
+
wechat_mobile = OAuth2Login(
|
|
35
|
+
api_key="your_api_key",
|
|
36
|
+
provider="wechat",
|
|
37
|
+
login_type="mobile"
|
|
38
|
+
)
|
|
39
|
+
user_info, success = await wechat_mobile.login()
|
|
40
|
+
|
|
41
|
+
# 支持refresh_token的提供商(如Google、GitLab)
|
|
42
|
+
if user_info.get("refresh_token"):
|
|
43
|
+
token_data, success = await google.refresh_access_token(
|
|
44
|
+
user_info.get("refresh_token")
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# 提供商端点映射
|
|
50
|
+
PROVIDER_ENDPOINTS = {
|
|
51
|
+
"google": {
|
|
52
|
+
"auth_url": "/api/unified-login/google/auth-url",
|
|
53
|
+
"wait_login": "/api/unified-login/google/wait-login",
|
|
54
|
+
"refresh_token": "/api/unified-login/google/refresh-token",
|
|
55
|
+
},
|
|
56
|
+
"wechat": {
|
|
57
|
+
"auth_url": "/api/unified-login/wechat/auth-url",
|
|
58
|
+
"wait_login": "/api/unified-login/wechat/wait-login",
|
|
59
|
+
},
|
|
60
|
+
"wechat-official": {
|
|
61
|
+
"auth_url": "/api/unified-login/wechat-official/auth-url",
|
|
62
|
+
"wait_login": "/api/unified-login/wechat-official/wait-login",
|
|
63
|
+
},
|
|
64
|
+
"github": {
|
|
65
|
+
"auth_url": "/api/unified-login/github/auth-url",
|
|
66
|
+
"wait_login": "/api/unified-login/github/wait-login",
|
|
67
|
+
},
|
|
68
|
+
"gitlab": {
|
|
69
|
+
"auth_url": "/api/unified-login/gitlab/auth-url",
|
|
70
|
+
"wait_login": "/api/unified-login/gitlab/wait-login",
|
|
71
|
+
"refresh_token": "/api/unified-login/gitlab/refresh-token",
|
|
72
|
+
},
|
|
73
|
+
"douyin": {
|
|
74
|
+
"auth_url": "/api/unified-login/douyin/auth-url",
|
|
75
|
+
"wait_login": "/api/unified-login/douyin/wait-login",
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
api_key: str,
|
|
82
|
+
provider: str,
|
|
83
|
+
login_type: Optional[str] = None
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
description:
|
|
87
|
+
初始化OAuth2登录客户端
|
|
88
|
+
parameters:
|
|
89
|
+
api_key(str): API密钥
|
|
90
|
+
provider(str): 提供商名称,可选值:google, wechat, github, gitlab, douyin
|
|
91
|
+
login_type(str, optional): 登录类型,仅对微信有效,可选值:desktop(PC端扫码), mobile(手机端)
|
|
92
|
+
"""
|
|
93
|
+
super().__init__(api_key)
|
|
94
|
+
self.provider = provider.lower()
|
|
95
|
+
self.login_type = login_type
|
|
96
|
+
|
|
97
|
+
# 验证提供商是否支持
|
|
98
|
+
if self.provider not in self.PROVIDER_ENDPOINTS:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"不支持的提供商: {provider}。"
|
|
101
|
+
f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 微信特殊处理:根据login_type选择不同的端点
|
|
105
|
+
if self.provider == "wechat":
|
|
106
|
+
if login_type == "mobile":
|
|
107
|
+
self.provider = "wechat-official"
|
|
108
|
+
|
|
109
|
+
def _get_endpoint(self, endpoint_type: str) -> str:
|
|
110
|
+
"""
|
|
111
|
+
description:
|
|
112
|
+
获取指定类型的端点URL
|
|
113
|
+
parameters:
|
|
114
|
+
endpoint_type(str): 端点类型,可选值:auth_url, wait_login, refresh_token
|
|
115
|
+
return:
|
|
116
|
+
endpoint(str): 端点URL
|
|
117
|
+
"""
|
|
118
|
+
endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
|
|
119
|
+
endpoint = endpoints.get(endpoint_type)
|
|
120
|
+
if not endpoint:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
|
|
123
|
+
)
|
|
124
|
+
return endpoint
|
|
125
|
+
|
|
126
|
+
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
127
|
+
"""
|
|
128
|
+
description:
|
|
129
|
+
获取OAuth授权URL
|
|
130
|
+
return:
|
|
131
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
132
|
+
success(bool): 是否成功
|
|
133
|
+
"""
|
|
134
|
+
endpoint = self._get_endpoint("auth_url")
|
|
135
|
+
data, success = await self._request("POST", endpoint)
|
|
136
|
+
if not success:
|
|
137
|
+
return None, False
|
|
138
|
+
auth_url = data.get("auth_url")
|
|
139
|
+
if not auth_url:
|
|
140
|
+
return None, False
|
|
141
|
+
return data, True
|
|
142
|
+
|
|
143
|
+
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
144
|
+
"""
|
|
145
|
+
description:
|
|
146
|
+
从授权URL中提取state参数
|
|
147
|
+
parameters:
|
|
148
|
+
auth_url(str, optional): 授权URL
|
|
149
|
+
return:
|
|
150
|
+
state(str, optional): state参数值,如果提取失败则返回None
|
|
151
|
+
"""
|
|
152
|
+
if not auth_url:
|
|
153
|
+
return None
|
|
154
|
+
try:
|
|
155
|
+
parsed = urllib.parse.urlparse(auth_url)
|
|
156
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
157
|
+
values = query.get("state")
|
|
158
|
+
if values:
|
|
159
|
+
return values[0]
|
|
160
|
+
except Exception:
|
|
161
|
+
return None
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
async def _wait_for_login(
|
|
165
|
+
self, state: str, timeout: int
|
|
166
|
+
) -> Tuple[Dict, bool]:
|
|
167
|
+
"""
|
|
168
|
+
description:
|
|
169
|
+
等待登录结果,轮询检查登录状态
|
|
170
|
+
parameters:
|
|
171
|
+
state(str): 登录状态标识
|
|
172
|
+
timeout(int): 超时时间(秒)
|
|
173
|
+
return:
|
|
174
|
+
user_info(dict): 用户信息字典
|
|
175
|
+
success(bool): 是否成功
|
|
176
|
+
"""
|
|
177
|
+
interval = 2
|
|
178
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
179
|
+
endpoint = self._get_endpoint("wait_login")
|
|
180
|
+
|
|
181
|
+
for _ in range(total_checks):
|
|
182
|
+
status, response = await self._request_raw(
|
|
183
|
+
"POST",
|
|
184
|
+
endpoint,
|
|
185
|
+
json={"state": state},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if status == 200 and response.get("success") is True:
|
|
189
|
+
return response.get("data", {}), True
|
|
190
|
+
|
|
191
|
+
if status in (400, 408):
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
await asyncio.sleep(interval)
|
|
195
|
+
|
|
196
|
+
return {}, False
|
|
197
|
+
|
|
198
|
+
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
199
|
+
"""
|
|
200
|
+
description:
|
|
201
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
202
|
+
parameters:
|
|
203
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
204
|
+
return:
|
|
205
|
+
user_info(dict): 用户信息字典
|
|
206
|
+
success(bool): 是否成功
|
|
207
|
+
"""
|
|
208
|
+
auth_data, success = await self._get_auth_url()
|
|
209
|
+
if not success or not auth_data:
|
|
210
|
+
return {}, False
|
|
211
|
+
|
|
212
|
+
auth_url = auth_data.get("auth_url")
|
|
213
|
+
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
214
|
+
|
|
215
|
+
if not auth_url or not state:
|
|
216
|
+
return {}, False
|
|
217
|
+
|
|
218
|
+
webbrowser.open(auth_url, new=2)
|
|
219
|
+
|
|
220
|
+
return await self._wait_for_login(state, timeout)
|
|
221
|
+
|
|
222
|
+
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
223
|
+
"""
|
|
224
|
+
description:
|
|
225
|
+
使用refresh_token刷新access_token(仅支持Google和GitLab)
|
|
226
|
+
parameters:
|
|
227
|
+
refresh_token(str): OAuth refresh_token
|
|
228
|
+
return:
|
|
229
|
+
token_data(dict): 包含新的access_token和可能的refresh_token的字典
|
|
230
|
+
success(bool): 是否成功
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
endpoint = self._get_endpoint("refresh_token")
|
|
234
|
+
except ValueError:
|
|
235
|
+
return {}, False
|
|
236
|
+
|
|
237
|
+
data, success = await self._request(
|
|
238
|
+
"POST",
|
|
239
|
+
endpoint,
|
|
240
|
+
json={"refresh_token": refresh_token},
|
|
241
|
+
)
|
|
242
|
+
if not success:
|
|
243
|
+
return {}, False
|
|
244
|
+
return data, True
|
|
245
|
+
|
|
246
|
+
|
|
9
247
|
class GoogleLogin(AsyncClient):
|
|
10
248
|
"""
|
|
11
249
|
Google OAuth2 登录客户端
|
|
@@ -30,6 +268,13 @@ class GoogleLogin(AsyncClient):
|
|
|
30
268
|
super().__init__(api_key)
|
|
31
269
|
|
|
32
270
|
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
271
|
+
"""
|
|
272
|
+
description:
|
|
273
|
+
获取Google OAuth授权URL
|
|
274
|
+
return:
|
|
275
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
276
|
+
success(bool): 是否成功
|
|
277
|
+
"""
|
|
33
278
|
data, success = await self._request(
|
|
34
279
|
"POST", "/api/unified-login/google/auth-url"
|
|
35
280
|
)
|
|
@@ -42,10 +287,13 @@ class GoogleLogin(AsyncClient):
|
|
|
42
287
|
|
|
43
288
|
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
44
289
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
290
|
+
description:
|
|
291
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
292
|
+
parameters:
|
|
293
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
294
|
+
return:
|
|
295
|
+
user_info(dict): 用户信息字典
|
|
296
|
+
success(bool): 是否成功
|
|
49
297
|
"""
|
|
50
298
|
auth_data, success = await self._get_auth_url()
|
|
51
299
|
if not success or not auth_data:
|
|
@@ -77,6 +325,16 @@ class GoogleLogin(AsyncClient):
|
|
|
77
325
|
async def _wait_for_google_login(
|
|
78
326
|
self, state: str, timeout: int
|
|
79
327
|
) -> Tuple[Dict, bool]:
|
|
328
|
+
"""
|
|
329
|
+
description:
|
|
330
|
+
等待Google登录结果,轮询检查登录状态
|
|
331
|
+
parameters:
|
|
332
|
+
state(str): 登录状态标识
|
|
333
|
+
timeout(int): 超时时间(秒)
|
|
334
|
+
return:
|
|
335
|
+
user_info(dict): 用户信息字典
|
|
336
|
+
success(bool): 是否成功
|
|
337
|
+
"""
|
|
80
338
|
interval = 2
|
|
81
339
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
82
340
|
|
|
@@ -99,19 +357,13 @@ class GoogleLogin(AsyncClient):
|
|
|
99
357
|
|
|
100
358
|
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
101
359
|
"""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
google = GoogleLogin(api_key="your_api_key")
|
|
110
|
-
token_data, success = await google.refresh_access_token(refresh_token="your_refresh_token")
|
|
111
|
-
if success:
|
|
112
|
-
new_access_token = token_data.get("access_token")
|
|
113
|
-
new_refresh_token = token_data.get("refresh_token") # 可能为None
|
|
114
|
-
```
|
|
360
|
+
description:
|
|
361
|
+
使用refresh_token刷新access_token
|
|
362
|
+
parameters:
|
|
363
|
+
refresh_token(str): Google OAuth refresh_token
|
|
364
|
+
return:
|
|
365
|
+
token_data(dict): 包含新的access_token和可能的refresh_token的字典
|
|
366
|
+
success(bool): 是否成功
|
|
115
367
|
"""
|
|
116
368
|
data, success = await self._request(
|
|
117
369
|
"POST",
|
|
@@ -146,9 +398,13 @@ class WechatLogin(AsyncClient):
|
|
|
146
398
|
|
|
147
399
|
async def _get_auth_url(self, login_type: str = "desktop") -> Tuple[Optional[Dict[str, str]], bool]:
|
|
148
400
|
"""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
:
|
|
401
|
+
description:
|
|
402
|
+
获取微信授权URL
|
|
403
|
+
parameters:
|
|
404
|
+
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
405
|
+
return:
|
|
406
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
407
|
+
success(bool): 是否成功
|
|
152
408
|
"""
|
|
153
409
|
if login_type == "mobile":
|
|
154
410
|
# 微信公众号OAuth登录(手机端)
|
|
@@ -167,20 +423,14 @@ class WechatLogin(AsyncClient):
|
|
|
167
423
|
|
|
168
424
|
async def login(self, login_type: str = "desktop", timeout: int = 180) -> Tuple[Dict, bool]:
|
|
169
425
|
"""
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
wechat = WechatLogin(api_key="your_api_key")
|
|
179
|
-
# PC端扫码登录
|
|
180
|
-
user_info, success = await wechat.login(login_type="desktop")
|
|
181
|
-
# 微信公众号登录(手机端)
|
|
182
|
-
user_info, success = await wechat.login(login_type="mobile")
|
|
183
|
-
```
|
|
426
|
+
description:
|
|
427
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
428
|
+
parameters:
|
|
429
|
+
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
430
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
431
|
+
return:
|
|
432
|
+
user_info(dict): 用户信息字典
|
|
433
|
+
success(bool): 是否成功
|
|
184
434
|
"""
|
|
185
435
|
auth_data, success = await self._get_auth_url(login_type)
|
|
186
436
|
if not success or not auth_data:
|
|
@@ -197,6 +447,14 @@ class WechatLogin(AsyncClient):
|
|
|
197
447
|
return await self._wait_for_wechat_login(state, timeout, login_type)
|
|
198
448
|
|
|
199
449
|
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
450
|
+
"""
|
|
451
|
+
description:
|
|
452
|
+
从授权URL中提取state参数
|
|
453
|
+
parameters:
|
|
454
|
+
auth_url(str, optional): 授权URL
|
|
455
|
+
return:
|
|
456
|
+
state(str, optional): state参数值,如果提取失败则返回None
|
|
457
|
+
"""
|
|
200
458
|
if not auth_url:
|
|
201
459
|
return None
|
|
202
460
|
try:
|
|
@@ -213,11 +471,15 @@ class WechatLogin(AsyncClient):
|
|
|
213
471
|
self, state: str, timeout: int, login_type: str = "desktop"
|
|
214
472
|
) -> Tuple[Dict, bool]:
|
|
215
473
|
"""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
:
|
|
219
|
-
|
|
220
|
-
|
|
474
|
+
description:
|
|
475
|
+
等待微信登录结果,轮询检查登录状态
|
|
476
|
+
parameters:
|
|
477
|
+
state(str): 登录状态标识
|
|
478
|
+
timeout(int): 超时时间(秒)
|
|
479
|
+
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
480
|
+
return:
|
|
481
|
+
user_info(dict): 用户信息字典
|
|
482
|
+
success(bool): 是否成功
|
|
221
483
|
"""
|
|
222
484
|
interval = 2
|
|
223
485
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
@@ -261,6 +523,13 @@ class GitHubLogin(AsyncClient):
|
|
|
261
523
|
super().__init__(api_key)
|
|
262
524
|
|
|
263
525
|
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
526
|
+
"""
|
|
527
|
+
description:
|
|
528
|
+
获取GitHub OAuth授权URL
|
|
529
|
+
return:
|
|
530
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
531
|
+
success(bool): 是否成功
|
|
532
|
+
"""
|
|
264
533
|
data, success = await self._request(
|
|
265
534
|
"POST", "/api/unified-login/github/auth-url"
|
|
266
535
|
)
|
|
@@ -273,10 +542,13 @@ class GitHubLogin(AsyncClient):
|
|
|
273
542
|
|
|
274
543
|
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
275
544
|
"""
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
545
|
+
description:
|
|
546
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
547
|
+
parameters:
|
|
548
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
549
|
+
return:
|
|
550
|
+
user_info(dict): 用户信息字典
|
|
551
|
+
success(bool): 是否成功
|
|
280
552
|
"""
|
|
281
553
|
auth_data, success = await self._get_auth_url()
|
|
282
554
|
if not success or not auth_data:
|
|
@@ -293,6 +565,14 @@ class GitHubLogin(AsyncClient):
|
|
|
293
565
|
return await self._wait_for_github_login(state, timeout)
|
|
294
566
|
|
|
295
567
|
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
568
|
+
"""
|
|
569
|
+
description:
|
|
570
|
+
从授权URL中提取state参数
|
|
571
|
+
parameters:
|
|
572
|
+
auth_url(str, optional): 授权URL
|
|
573
|
+
return:
|
|
574
|
+
state(str, optional): state参数值,如果提取失败则返回None
|
|
575
|
+
"""
|
|
296
576
|
if not auth_url:
|
|
297
577
|
return None
|
|
298
578
|
try:
|
|
@@ -308,6 +588,16 @@ class GitHubLogin(AsyncClient):
|
|
|
308
588
|
async def _wait_for_github_login(
|
|
309
589
|
self, state: str, timeout: int
|
|
310
590
|
) -> Tuple[Dict, bool]:
|
|
591
|
+
"""
|
|
592
|
+
description:
|
|
593
|
+
等待GitHub登录结果,轮询检查登录状态
|
|
594
|
+
parameters:
|
|
595
|
+
state(str): 登录状态标识
|
|
596
|
+
timeout(int): 超时时间(秒)
|
|
597
|
+
return:
|
|
598
|
+
user_info(dict): 用户信息字典
|
|
599
|
+
success(bool): 是否成功
|
|
600
|
+
"""
|
|
311
601
|
interval = 2
|
|
312
602
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
313
603
|
|
|
@@ -344,6 +634,13 @@ class DouyinLogin(AsyncClient):
|
|
|
344
634
|
super().__init__(api_key)
|
|
345
635
|
|
|
346
636
|
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
637
|
+
"""
|
|
638
|
+
description:
|
|
639
|
+
获取抖音OAuth授权URL
|
|
640
|
+
return:
|
|
641
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
642
|
+
success(bool): 是否成功
|
|
643
|
+
"""
|
|
347
644
|
data, success = await self._request(
|
|
348
645
|
"POST", "/api/unified-login/douyin/auth-url"
|
|
349
646
|
)
|
|
@@ -356,10 +653,13 @@ class DouyinLogin(AsyncClient):
|
|
|
356
653
|
|
|
357
654
|
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
358
655
|
"""
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
656
|
+
description:
|
|
657
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
658
|
+
parameters:
|
|
659
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
660
|
+
return:
|
|
661
|
+
user_info(dict): 用户信息字典
|
|
662
|
+
success(bool): 是否成功
|
|
363
663
|
"""
|
|
364
664
|
auth_data, success = await self._get_auth_url()
|
|
365
665
|
if not success or not auth_data:
|
|
@@ -376,6 +676,14 @@ class DouyinLogin(AsyncClient):
|
|
|
376
676
|
return await self._wait_for_douyin_login(state, timeout)
|
|
377
677
|
|
|
378
678
|
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
679
|
+
"""
|
|
680
|
+
description:
|
|
681
|
+
从授权URL中提取state参数
|
|
682
|
+
parameters:
|
|
683
|
+
auth_url(str, optional): 授权URL
|
|
684
|
+
return:
|
|
685
|
+
state(str, optional): state参数值,如果提取失败则返回None
|
|
686
|
+
"""
|
|
379
687
|
if not auth_url:
|
|
380
688
|
return None
|
|
381
689
|
try:
|
|
@@ -391,6 +699,16 @@ class DouyinLogin(AsyncClient):
|
|
|
391
699
|
async def _wait_for_douyin_login(
|
|
392
700
|
self, state: str, timeout: int
|
|
393
701
|
) -> Tuple[Dict, bool]:
|
|
702
|
+
"""
|
|
703
|
+
description:
|
|
704
|
+
等待抖音登录结果,轮询检查登录状态
|
|
705
|
+
parameters:
|
|
706
|
+
state(str): 登录状态标识
|
|
707
|
+
timeout(int): 超时时间(秒)
|
|
708
|
+
return:
|
|
709
|
+
user_info(dict): 用户信息字典
|
|
710
|
+
success(bool): 是否成功
|
|
711
|
+
"""
|
|
394
712
|
interval = 2
|
|
395
713
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
396
714
|
|
|
@@ -494,6 +812,25 @@ class GitLabLogin(AsyncClient):
|
|
|
494
812
|
|
|
495
813
|
return {}, False
|
|
496
814
|
|
|
815
|
+
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
816
|
+
"""
|
|
817
|
+
description:
|
|
818
|
+
使用refresh_token刷新access_token
|
|
819
|
+
parameters:
|
|
820
|
+
refresh_token(str): GitLab OAuth refresh_token
|
|
821
|
+
return:
|
|
822
|
+
token_data(dict): 包含新的access_token和可能的refresh_token的字典
|
|
823
|
+
success(bool): 是否成功
|
|
824
|
+
"""
|
|
825
|
+
data, success = await self._request(
|
|
826
|
+
"POST",
|
|
827
|
+
"/api/unified-login/gitlab/refresh-token",
|
|
828
|
+
json={"refresh_token": refresh_token},
|
|
829
|
+
)
|
|
830
|
+
if not success:
|
|
831
|
+
return {}, False
|
|
832
|
+
return data, True
|
|
833
|
+
|
|
497
834
|
|
|
498
835
|
class SMSLogin(AsyncClient):
|
|
499
836
|
"""
|
|
@@ -503,60 +840,50 @@ class SMSLogin(AsyncClient):
|
|
|
503
840
|
```
|
|
504
841
|
sms = SMSLogin(api_key="your_api_key")
|
|
505
842
|
# 发送验证码
|
|
506
|
-
|
|
843
|
+
success = await sms.send_code(phone="13800138000")
|
|
507
844
|
if success:
|
|
508
845
|
# 验证验证码并登录
|
|
509
|
-
user_info, success = await sms.login(
|
|
846
|
+
user_info, success = await sms.login(phone="13800138000", code="123456")
|
|
510
847
|
```
|
|
511
848
|
"""
|
|
512
849
|
|
|
513
850
|
def __init__(self, api_key: str):
|
|
514
851
|
super().__init__(api_key)
|
|
515
852
|
|
|
516
|
-
async def send_code(self, phone: str) ->
|
|
853
|
+
async def send_code(self, phone: str) -> bool:
|
|
517
854
|
"""
|
|
518
855
|
发送短信验证码
|
|
519
856
|
|
|
520
857
|
:param phone: 手机号码
|
|
521
|
-
:return:
|
|
858
|
+
:return: success 是否成功
|
|
522
859
|
"""
|
|
523
860
|
data, success = await self._request(
|
|
524
861
|
"POST", "/api/unified-login/sms/send-code", json={"phone": phone}
|
|
525
862
|
)
|
|
526
|
-
|
|
527
|
-
return None, False
|
|
528
|
-
state = data.get("state")
|
|
529
|
-
if not state:
|
|
530
|
-
return None, False
|
|
531
|
-
return state, True
|
|
863
|
+
return bool(success)
|
|
532
864
|
|
|
533
|
-
async def verify_code(self,
|
|
865
|
+
async def verify_code(self, phone: str, code: str) -> bool:
|
|
534
866
|
"""
|
|
535
867
|
验证短信验证码
|
|
536
868
|
|
|
537
|
-
:param
|
|
869
|
+
:param phone: 手机号码
|
|
538
870
|
:param code: 验证码
|
|
539
|
-
:return:
|
|
871
|
+
:return: success 是否成功
|
|
540
872
|
"""
|
|
541
|
-
|
|
873
|
+
_, success = await self._request(
|
|
542
874
|
"POST",
|
|
543
875
|
"/api/unified-login/sms/verify-code",
|
|
544
|
-
json={"
|
|
876
|
+
json={"phone": phone, "code": code},
|
|
545
877
|
)
|
|
546
|
-
|
|
547
|
-
return None, False
|
|
548
|
-
new_state = data.get("state")
|
|
549
|
-
if not new_state:
|
|
550
|
-
return None, False
|
|
551
|
-
return new_state, True
|
|
878
|
+
return bool(success)
|
|
552
879
|
|
|
553
880
|
async def _wait_for_login(
|
|
554
|
-
self,
|
|
881
|
+
self, phone: str, timeout: int
|
|
555
882
|
) -> Tuple[Dict, bool]:
|
|
556
883
|
"""
|
|
557
884
|
等待短信登录结果
|
|
558
885
|
|
|
559
|
-
:param
|
|
886
|
+
:param phone: 手机号码
|
|
560
887
|
:param timeout: 超时时间(秒)
|
|
561
888
|
"""
|
|
562
889
|
interval = 2
|
|
@@ -566,7 +893,7 @@ class SMSLogin(AsyncClient):
|
|
|
566
893
|
status, response = await self._request_raw(
|
|
567
894
|
"POST",
|
|
568
895
|
"/api/unified-login/sms/wait-sms-login",
|
|
569
|
-
json={"
|
|
896
|
+
json={"phone": phone},
|
|
570
897
|
)
|
|
571
898
|
|
|
572
899
|
if status == 200 and response.get("success") is True:
|
|
@@ -580,21 +907,21 @@ class SMSLogin(AsyncClient):
|
|
|
580
907
|
return {}, False
|
|
581
908
|
|
|
582
909
|
async def login(
|
|
583
|
-
self,
|
|
910
|
+
self, phone: str, code: str, timeout: int = 180
|
|
584
911
|
) -> Tuple[Dict, bool]:
|
|
585
912
|
"""
|
|
586
913
|
验证验证码并等待登录结果
|
|
587
914
|
|
|
588
|
-
:param
|
|
915
|
+
:param phone: 手机号码
|
|
589
916
|
:param code: 验证码
|
|
590
917
|
:param timeout: 等待登录结果的超时时间(秒)
|
|
591
918
|
:return: (用户信息, 是否成功)
|
|
592
919
|
"""
|
|
593
|
-
|
|
594
|
-
if not success
|
|
920
|
+
success = await self.verify_code(phone, code)
|
|
921
|
+
if not success:
|
|
595
922
|
return {}, False
|
|
596
923
|
|
|
597
|
-
return await self._wait_for_login(
|
|
924
|
+
return await self._wait_for_login(phone, timeout)
|
|
598
925
|
|
|
599
926
|
|
|
600
927
|
class EmailLogin(AsyncClient):
|
|
@@ -605,60 +932,50 @@ class EmailLogin(AsyncClient):
|
|
|
605
932
|
```
|
|
606
933
|
email = EmailLogin(api_key="your_api_key")
|
|
607
934
|
# 发送验证码
|
|
608
|
-
|
|
935
|
+
success = await email.send_code(email="user@example.com")
|
|
609
936
|
if success:
|
|
610
937
|
# 验证验证码并登录
|
|
611
|
-
user_info, success = await email.login(
|
|
938
|
+
user_info, success = await email.login(email="user@example.com", code="123456")
|
|
612
939
|
```
|
|
613
940
|
"""
|
|
614
941
|
|
|
615
942
|
def __init__(self, api_key: str):
|
|
616
943
|
super().__init__(api_key)
|
|
617
944
|
|
|
618
|
-
async def send_code(self, email: str) ->
|
|
945
|
+
async def send_code(self, email: str) -> bool:
|
|
619
946
|
"""
|
|
620
947
|
发送邮箱验证码
|
|
621
948
|
|
|
622
949
|
:param email: 邮箱地址
|
|
623
|
-
:return:
|
|
950
|
+
:return: success 是否成功
|
|
624
951
|
"""
|
|
625
|
-
|
|
952
|
+
_, success = await self._request(
|
|
626
953
|
"POST", "/api/unified-login/email/send-code", json={"email": email}
|
|
627
954
|
)
|
|
628
|
-
|
|
629
|
-
return None, False
|
|
630
|
-
state = data.get("state")
|
|
631
|
-
if not state:
|
|
632
|
-
return None, False
|
|
633
|
-
return state, True
|
|
955
|
+
return bool(success)
|
|
634
956
|
|
|
635
|
-
async def verify_code(self,
|
|
957
|
+
async def verify_code(self, email: str, code: str) -> bool:
|
|
636
958
|
"""
|
|
637
959
|
验证邮箱验证码
|
|
638
960
|
|
|
639
|
-
:param
|
|
961
|
+
:param email: 邮箱地址
|
|
640
962
|
:param code: 验证码
|
|
641
|
-
:return:
|
|
963
|
+
:return: success 是否成功
|
|
642
964
|
"""
|
|
643
|
-
|
|
965
|
+
_, success = await self._request(
|
|
644
966
|
"POST",
|
|
645
967
|
"/api/unified-login/email/verify-code",
|
|
646
|
-
json={"
|
|
968
|
+
json={"email": email, "code": code},
|
|
647
969
|
)
|
|
648
|
-
|
|
649
|
-
return None, False
|
|
650
|
-
new_state = data.get("state")
|
|
651
|
-
if not new_state:
|
|
652
|
-
return None, False
|
|
653
|
-
return new_state, True
|
|
970
|
+
return bool(success)
|
|
654
971
|
|
|
655
972
|
async def _wait_for_login(
|
|
656
|
-
self,
|
|
973
|
+
self, email: str, timeout: int
|
|
657
974
|
) -> Tuple[Dict, bool]:
|
|
658
975
|
"""
|
|
659
976
|
等待邮箱登录结果
|
|
660
977
|
|
|
661
|
-
:param
|
|
978
|
+
:param email: 邮箱地址
|
|
662
979
|
:param timeout: 超时时间(秒)
|
|
663
980
|
"""
|
|
664
981
|
interval = 2
|
|
@@ -668,7 +985,7 @@ class EmailLogin(AsyncClient):
|
|
|
668
985
|
status, response = await self._request_raw(
|
|
669
986
|
"POST",
|
|
670
987
|
"/api/unified-login/email/wait-email-login",
|
|
671
|
-
json={"
|
|
988
|
+
json={"email": email},
|
|
672
989
|
)
|
|
673
990
|
|
|
674
991
|
if status == 200 and response.get("success") is True:
|
|
@@ -682,18 +999,18 @@ class EmailLogin(AsyncClient):
|
|
|
682
999
|
return {}, False
|
|
683
1000
|
|
|
684
1001
|
async def login(
|
|
685
|
-
self,
|
|
1002
|
+
self, email: str, code: str, timeout: int = 180
|
|
686
1003
|
) -> Tuple[Dict, bool]:
|
|
687
1004
|
"""
|
|
688
1005
|
验证验证码并等待登录结果
|
|
689
1006
|
|
|
690
|
-
:param
|
|
1007
|
+
:param email: 邮箱地址
|
|
691
1008
|
:param code: 验证码
|
|
692
1009
|
:param timeout: 等待登录结果的超时时间(秒)
|
|
693
1010
|
:return: (用户信息, 是否成功)
|
|
694
1011
|
"""
|
|
695
|
-
|
|
696
|
-
if not success
|
|
1012
|
+
success = await self.verify_code(email, code)
|
|
1013
|
+
if not success:
|
|
697
1014
|
return {}, False
|
|
698
1015
|
|
|
699
|
-
return await self._wait_for_login(
|
|
1016
|
+
return await self._wait_for_login(email, timeout)
|
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/cron/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/feedback/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/order/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/product/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/product/product.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/project/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/project/project.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty/user/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.1.9 → pixelarraythirdparty-1.2.1}/pixelarraythirdparty.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|