pixelarraythirdparty 1.2.2__py3-none-any.whl → 1.2.4__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.
@@ -13,7 +13,7 @@ PixelArray 第三方微服务客户端
13
13
  - project: 项目管理模块
14
14
  """
15
15
 
16
- __version__ = "1.2.2"
16
+ __version__ = "1.2.4"
17
17
  __author__ = "Lu qi"
18
18
  __email__ = "qi.lu@pixelarrayai.com"
19
19
 
@@ -9,43 +9,29 @@ from pixelarraythirdparty.client import AsyncClient
9
9
  class OAuth2Login(AsyncClient):
10
10
  """
11
11
  统一的 OAuth2 登录客户端基类
12
-
12
+
13
13
  支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音等)
14
14
  通过配置不同的端点来支持不同的提供商
15
-
15
+
16
16
  使用示例:
17
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(
18
+ # 服务端使用场景(推荐)
19
+ oauth2_login = OAuth2Login(
35
20
  api_key="your_api_key",
36
- provider="wechat",
37
- login_type="mobile"
21
+ provider="google" # 支持的提供商:google, wechat, github, gitlab, douyin
38
22
  )
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
- )
23
+ # 1. 获取授权URL
24
+ auth_data, success = await oauth2_login.get_auth_url()
25
+ if success:
26
+ auth_url = auth_data.get("auth_url")
27
+ state = auth_data.get("state")
28
+ # 将auth_url返回给前端,让用户点击授权
29
+ # 2. 等待登录结果(在服务端轮询)
30
+ user_info, success = await oauth2_login.wait_for_login(state, timeout=180)
31
+ print(user_info, success)
46
32
  ```
47
33
  """
48
-
34
+
49
35
  # 提供商端点映射
50
36
  PROVIDER_ENDPOINTS = {
51
37
  "google": {
@@ -75,13 +61,8 @@ class OAuth2Login(AsyncClient):
75
61
  "wait_login": "/api/unified-login/douyin/wait-login",
76
62
  },
77
63
  }
78
-
79
- def __init__(
80
- self,
81
- api_key: str,
82
- provider: str,
83
- login_type: Optional[str] = None
84
- ):
64
+
65
+ def __init__(self, api_key: str, provider: str, login_type: Optional[str] = None):
85
66
  """
86
67
  description:
87
68
  初始化OAuth2登录客户端
@@ -93,19 +74,19 @@ class OAuth2Login(AsyncClient):
93
74
  super().__init__(api_key)
94
75
  self.provider = provider.lower()
95
76
  self.login_type = login_type
96
-
77
+
97
78
  # 验证提供商是否支持
98
79
  if self.provider not in self.PROVIDER_ENDPOINTS:
99
80
  raise ValueError(
100
81
  f"不支持的提供商: {provider}。"
101
82
  f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
102
83
  )
103
-
84
+
104
85
  # 微信特殊处理:根据login_type选择不同的端点
105
86
  if self.provider == "wechat":
106
87
  if login_type == "mobile":
107
88
  self.provider = "wechat-official"
108
-
89
+
109
90
  def _get_endpoint(self, endpoint_type: str) -> str:
110
91
  """
111
92
  description:
@@ -118,15 +99,16 @@ class OAuth2Login(AsyncClient):
118
99
  endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
119
100
  endpoint = endpoints.get(endpoint_type)
120
101
  if not endpoint:
121
- raise ValueError(
122
- f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
123
- )
102
+ raise ValueError(f"提供商 {self.provider} 不支持 {endpoint_type} 端点")
124
103
  return endpoint
125
-
126
- async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
104
+
105
+ async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
127
106
  """
128
107
  description:
129
- 获取OAuth授权URL
108
+ 获取OAuth授权URL(公共方法,供服务端调用)
109
+
110
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
111
+ 获取到state后,使用wait_for_login方法等待登录结果。
130
112
  return:
131
113
  auth_data(dict, optional): 授权数据字典,包含auth_url和state
132
114
  success(bool): 是否成功
@@ -139,37 +121,17 @@ class OAuth2Login(AsyncClient):
139
121
  if not auth_url:
140
122
  return None, False
141
123
  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]:
124
+
125
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
167
126
  """
168
127
  description:
169
- 等待登录结果,轮询检查登录状态
128
+ 等待登录结果,轮询检查登录状态(公共方法,供服务端调用)
129
+
130
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
131
+ 此方法会持续轮询直到登录成功或超时。
170
132
  parameters:
171
- state(str): 登录状态标识
172
- timeout(int): 超时时间(秒)
133
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
134
+ timeout(int, optional): 超时时间(秒),默认为180
173
135
  return:
174
136
  user_info(dict): 用户信息字典
175
137
  success(bool): 是否成功
@@ -177,53 +139,29 @@ class OAuth2Login(AsyncClient):
177
139
  interval = 2
178
140
  total_checks = max(1, timeout // interval) if timeout > 0 else 1
179
141
  endpoint = self._get_endpoint("wait_login")
180
-
142
+
181
143
  for _ in range(total_checks):
182
144
  status, response = await self._request_raw(
183
145
  "POST",
184
146
  endpoint,
185
147
  json={"state": state},
186
148
  )
187
-
149
+
188
150
  if status == 200 and response.get("success") is True:
189
151
  return response.get("data", {}), True
190
-
152
+
191
153
  if status in (400, 408):
192
154
  break
193
-
155
+
194
156
  await asyncio.sleep(interval)
195
-
157
+
196
158
  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
-
159
+
222
160
  async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
223
161
  """
224
162
  description:
225
163
  使用refresh_token刷新access_token(仅支持Google和GitLab)
226
-
164
+
227
165
  注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
228
166
  旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
229
167
  Google 也可能在某些情况下返回新的 refresh_token。
@@ -237,7 +175,7 @@ class OAuth2Login(AsyncClient):
237
175
  endpoint = self._get_endpoint("refresh_token")
238
176
  except ValueError:
239
177
  return {}, False
240
-
178
+
241
179
  data, success = await self._request(
242
180
  "POST",
243
181
  endpoint,
@@ -254,27 +192,32 @@ class GoogleLogin(AsyncClient):
254
192
 
255
193
  使用示例:
256
194
  ```
195
+ # 服务端使用场景(推荐)
257
196
  google = GoogleLogin(api_key="your_api_key")
258
- user_info, success = await google.login()
197
+ # 1. 获取授权URL
198
+ auth_data, success = await google.get_auth_url()
259
199
  if success:
260
- access_token = user_info.get("access_token")
261
- refresh_token = user_info.get("refresh_token")
262
-
263
- # 使用refresh_token刷新access_token
264
- if refresh_token:
265
- token_data, success = await google.refresh_access_token(refresh_token)
266
- if success:
267
- new_access_token = token_data.get("access_token")
200
+ auth_url = auth_data.get("auth_url")
201
+ state = auth_data.get("state")
202
+ # 将auth_url返回给前端,让用户点击授权
203
+ # 2. 等待登录结果(在服务端轮询)
204
+ user_info, success = await google.wait_for_login(state, timeout=180)
205
+ if success:
206
+ access_token = user_info.get("access_token")
207
+ refresh_token = user_info.get("refresh_token")
268
208
  ```
269
209
  """
270
210
 
271
211
  def __init__(self, api_key: str):
272
212
  super().__init__(api_key)
273
213
 
274
- async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
214
+ async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
275
215
  """
276
216
  description:
277
- 获取Google OAuth授权URL
217
+ 获取Google OAuth授权URL(公共方法,供服务端调用)
218
+
219
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
220
+ 获取到state后,使用wait_for_login方法等待登录结果。
278
221
  return:
279
222
  auth_data(dict, optional): 授权数据字典,包含auth_url和state
280
223
  success(bool): 是否成功
@@ -289,52 +232,16 @@ class GoogleLogin(AsyncClient):
289
232
  return None, False
290
233
  return data, True
291
234
 
292
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
235
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
293
236
  """
294
237
  description:
295
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
296
- parameters:
297
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
298
- return:
299
- user_info(dict): 用户信息字典
300
- success(bool): 是否成功
301
- """
302
- auth_data, success = await self._get_auth_url()
303
- if not success or not auth_data:
304
- return {}, False
305
-
306
- auth_url = auth_data.get("auth_url")
307
- state = auth_data.get("state") or self._extract_state(auth_url)
308
-
309
- if not auth_url or not state:
310
- return {}, False
238
+ 等待Google登录结果,轮询检查登录状态(公共方法,供服务端调用)
311
239
 
312
- webbrowser.open(auth_url, new=2)
313
-
314
- return await self._wait_for_google_login(state, timeout)
315
-
316
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
317
- if not auth_url:
318
- return None
319
- try:
320
- parsed = urllib.parse.urlparse(auth_url)
321
- query = urllib.parse.parse_qs(parsed.query)
322
- values = query.get("state")
323
- if values:
324
- return values[0]
325
- except Exception:
326
- return None
327
- return None
328
-
329
- async def _wait_for_google_login(
330
- self, state: str, timeout: int
331
- ) -> Tuple[Dict, bool]:
332
- """
333
- description:
334
- 等待Google登录结果,轮询检查登录状态
240
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
241
+ 此方法会持续轮询直到登录成功或超时。
335
242
  parameters:
336
- state(str): 登录状态标识
337
- timeout(int): 超时时间(秒)
243
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
244
+ timeout(int, optional): 超时时间(秒),默认为180
338
245
  return:
339
246
  user_info(dict): 用户信息字典
340
247
  success(bool): 是否成功
@@ -382,28 +289,49 @@ class GoogleLogin(AsyncClient):
382
289
  class WechatLogin(AsyncClient):
383
290
  """
384
291
  微信 OAuth2 登录客户端
385
-
292
+
386
293
  支持两种登录方式:
387
294
  - desktop: PC端扫码登录(使用微信开放平台)
388
295
  - mobile: 微信公众号OAuth登录(手机端微信内打开)
389
296
 
390
297
  使用示例:
391
298
  ```
299
+ # 服务端使用场景(推荐)
392
300
  wechat = WechatLogin(api_key="your_api_key")
393
301
  # PC端扫码登录
394
- user_info, success = await wechat.login(login_type="desktop")
302
+ auth_data, success = await wechat.get_auth_url(login_type="desktop")
303
+ if success:
304
+ auth_url = auth_data.get("auth_url")
305
+ state = auth_data.get("state")
306
+ # 将auth_url返回给前端,让用户扫码授权
307
+ # 等待登录结果
308
+ user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="desktop")
309
+ print(user_info, success)
310
+
395
311
  # 微信公众号登录(手机端)
396
- user_info, success = await wechat.login(login_type="mobile")
312
+ auth_data, success = await wechat.get_auth_url(login_type="mobile")
313
+ if success:
314
+ auth_url = auth_data.get("auth_url")
315
+ state = auth_data.get("state")
316
+ # 将auth_url返回给前端,让用户在微信内打开授权
317
+ # 等待登录结果
318
+ user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="mobile")
319
+ print(user_info, success)
397
320
  ```
398
321
  """
399
322
 
400
323
  def __init__(self, api_key: str):
401
324
  super().__init__(api_key)
402
325
 
403
- async def _get_auth_url(self, login_type: str = "desktop") -> Tuple[Optional[Dict[str, str]], bool]:
326
+ async def get_auth_url(
327
+ self, login_type: str = "desktop"
328
+ ) -> Tuple[Optional[Dict[str, str]], bool]:
404
329
  """
405
330
  description:
406
- 获取微信授权URL
331
+ 获取微信授权URL(公共方法,供服务端调用)
332
+
333
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
334
+ 获取到state后,使用wait_for_login方法等待登录结果。
407
335
  parameters:
408
336
  login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
409
337
  return:
@@ -416,7 +344,7 @@ class WechatLogin(AsyncClient):
416
344
  else:
417
345
  # PC端扫码登录
418
346
  endpoint = "/api/unified-login/wechat/auth-url"
419
-
347
+
420
348
  data, success = await self._request("POST", endpoint)
421
349
  if not success:
422
350
  return None, False
@@ -425,61 +353,18 @@ class WechatLogin(AsyncClient):
425
353
  return None, False
426
354
  return data, True
427
355
 
428
- async def login(self, login_type: str = "desktop", timeout: int = 180) -> Tuple[Dict, bool]:
429
- """
430
- description:
431
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
432
- parameters:
433
- login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
434
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
435
- return:
436
- user_info(dict): 用户信息字典
437
- success(bool): 是否成功
438
- """
439
- auth_data, success = await self._get_auth_url(login_type)
440
- if not success or not auth_data:
441
- return {}, False
442
-
443
- auth_url = auth_data.get("auth_url")
444
- state = auth_data.get("state") or self._extract_state(auth_url)
445
-
446
- if not auth_url or not state:
447
- return {}, False
448
-
449
- webbrowser.open(auth_url, new=2)
450
-
451
- return await self._wait_for_wechat_login(state, timeout, login_type)
452
-
453
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
454
- """
455
- description:
456
- 从授权URL中提取state参数
457
- parameters:
458
- auth_url(str, optional): 授权URL
459
- return:
460
- state(str, optional): state参数值,如果提取失败则返回None
461
- """
462
- if not auth_url:
463
- return None
464
- try:
465
- parsed = urllib.parse.urlparse(auth_url)
466
- query = urllib.parse.parse_qs(parsed.query)
467
- values = query.get("state")
468
- if values:
469
- return values[0]
470
- except Exception:
471
- return None
472
- return None
473
-
474
- async def _wait_for_wechat_login(
475
- self, state: str, timeout: int, login_type: str = "desktop"
356
+ async def wait_for_login(
357
+ self, state: str, timeout: int = 180, login_type: str = "desktop"
476
358
  ) -> Tuple[Dict, bool]:
477
359
  """
478
360
  description:
479
- 等待微信登录结果,轮询检查登录状态
361
+ 等待微信登录结果,轮询检查登录状态(公共方法,供服务端调用)
362
+
363
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
364
+ 此方法会持续轮询直到登录成功或超时。
480
365
  parameters:
481
- state(str): 登录状态标识
482
- timeout(int): 超时时间(秒)
366
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
367
+ timeout(int, optional): 超时时间(秒),默认为180
483
368
  login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
484
369
  return:
485
370
  user_info(dict): 用户信息字典
@@ -518,18 +403,30 @@ class GitHubLogin(AsyncClient):
518
403
 
519
404
  使用示例:
520
405
  ```
406
+ # 服务端使用场景(推荐)
521
407
  github = GitHubLogin(api_key="your_api_key")
522
- user_info, success = await github.login()
408
+ # 1. 获取授权URL
409
+ auth_data, success = await github.get_auth_url()
410
+ if success:
411
+ auth_url = auth_data.get("auth_url")
412
+ state = auth_data.get("state")
413
+ # 将auth_url返回给前端,让用户点击授权
414
+ # 2. 等待登录结果(在服务端轮询)
415
+ user_info, success = await github.wait_for_login(state, timeout=180)
416
+ print(user_info, success)
523
417
  ```
524
418
  """
525
419
 
526
420
  def __init__(self, api_key: str):
527
421
  super().__init__(api_key)
528
422
 
529
- async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
423
+ async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
530
424
  """
531
425
  description:
532
- 获取GitHub OAuth授权URL
426
+ 获取GitHub OAuth授权URL(公共方法,供服务端调用)
427
+
428
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
429
+ 获取到state后,使用wait_for_login方法等待登录结果。
533
430
  return:
534
431
  auth_data(dict, optional): 授权数据字典,包含auth_url和state
535
432
  success(bool): 是否成功
@@ -544,60 +441,16 @@ class GitHubLogin(AsyncClient):
544
441
  return None, False
545
442
  return data, True
546
443
 
547
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
444
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
548
445
  """
549
446
  description:
550
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
551
- parameters:
552
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
553
- return:
554
- user_info(dict): 用户信息字典
555
- success(bool): 是否成功
556
- """
557
- auth_data, success = await self._get_auth_url()
558
- if not success or not auth_data:
559
- return {}, False
560
-
561
- auth_url = auth_data.get("auth_url")
562
- state = auth_data.get("state") or self._extract_state(auth_url)
563
-
564
- if not auth_url or not state:
565
- return {}, False
447
+ 等待GitHub登录结果,轮询检查登录状态(公共方法,供服务端调用)
566
448
 
567
- webbrowser.open(auth_url, new=2)
568
-
569
- return await self._wait_for_github_login(state, timeout)
570
-
571
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
572
- """
573
- description:
574
- 从授权URL中提取state参数
575
- parameters:
576
- auth_url(str, optional): 授权URL
577
- return:
578
- state(str, optional): state参数值,如果提取失败则返回None
579
- """
580
- if not auth_url:
581
- return None
582
- try:
583
- parsed = urllib.parse.urlparse(auth_url)
584
- query = urllib.parse.parse_qs(parsed.query)
585
- values = query.get("state")
586
- if values:
587
- return values[0]
588
- except Exception:
589
- return None
590
- return None
591
-
592
- async def _wait_for_github_login(
593
- self, state: str, timeout: int
594
- ) -> Tuple[Dict, bool]:
595
- """
596
- description:
597
- 等待GitHub登录结果,轮询检查登录状态
449
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
450
+ 此方法会持续轮询直到登录成功或超时。
598
451
  parameters:
599
- state(str): 登录状态标识
600
- timeout(int): 超时时间(秒)
452
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
453
+ timeout(int, optional): 超时时间(秒),默认为180
601
454
  return:
602
455
  user_info(dict): 用户信息字典
603
456
  success(bool): 是否成功
@@ -629,18 +482,30 @@ class DouyinLogin(AsyncClient):
629
482
 
630
483
  使用示例:
631
484
  ```
485
+ # 服务端使用场景(推荐)
632
486
  douyin = DouyinLogin(api_key="your_api_key")
633
- user_info, success = await douyin.login()
487
+ # 1. 获取授权URL
488
+ auth_data, success = await douyin.get_auth_url()
489
+ if success:
490
+ auth_url = auth_data.get("auth_url")
491
+ state = auth_data.get("state")
492
+ # 将auth_url返回给前端,让用户点击授权
493
+ # 2. 等待登录结果(在服务端轮询)
494
+ user_info, success = await douyin.wait_for_login(state, timeout=180)
495
+ print(user_info, success)
634
496
  ```
635
497
  """
636
498
 
637
499
  def __init__(self, api_key: str):
638
500
  super().__init__(api_key)
639
501
 
640
- async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
502
+ async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
641
503
  """
642
504
  description:
643
- 获取抖音OAuth授权URL
505
+ 获取抖音OAuth授权URL(公共方法,供服务端调用)
506
+
507
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
508
+ 获取到state后,使用wait_for_login方法等待登录结果。
644
509
  return:
645
510
  auth_data(dict, optional): 授权数据字典,包含auth_url和state
646
511
  success(bool): 是否成功
@@ -655,60 +520,16 @@ class DouyinLogin(AsyncClient):
655
520
  return None, False
656
521
  return data, True
657
522
 
658
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
523
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
659
524
  """
660
525
  description:
661
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
662
- parameters:
663
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
664
- return:
665
- user_info(dict): 用户信息字典
666
- success(bool): 是否成功
667
- """
668
- auth_data, success = await self._get_auth_url()
669
- if not success or not auth_data:
670
- return {}, False
526
+ 等待抖音登录结果,轮询检查登录状态(公共方法,供服务端调用)
671
527
 
672
- auth_url = auth_data.get("auth_url")
673
- state = auth_data.get("state") or self._extract_state(auth_url)
674
-
675
- if not auth_url or not state:
676
- return {}, False
677
-
678
- webbrowser.open(auth_url, new=2)
679
-
680
- return await self._wait_for_douyin_login(state, timeout)
681
-
682
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
683
- """
684
- description:
685
- 从授权URL中提取state参数
686
- parameters:
687
- auth_url(str, optional): 授权URL
688
- return:
689
- state(str, optional): state参数值,如果提取失败则返回None
690
- """
691
- if not auth_url:
692
- return None
693
- try:
694
- parsed = urllib.parse.urlparse(auth_url)
695
- query = urllib.parse.parse_qs(parsed.query)
696
- values = query.get("state")
697
- if values:
698
- return values[0]
699
- except Exception:
700
- return None
701
- return None
702
-
703
- async def _wait_for_douyin_login(
704
- self, state: str, timeout: int
705
- ) -> Tuple[Dict, bool]:
706
- """
707
- description:
708
- 等待抖音登录结果,轮询检查登录状态
528
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
529
+ 此方法会持续轮询直到登录成功或超时。
709
530
  parameters:
710
- state(str): 登录状态标识
711
- timeout(int): 超时时间(秒)
531
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
532
+ timeout(int, optional): 超时时间(秒),默认为180
712
533
  return:
713
534
  user_info(dict): 用户信息字典
714
535
  success(bool): 是否成功
@@ -740,15 +561,34 @@ class GitLabLogin(AsyncClient):
740
561
 
741
562
  使用示例:
742
563
  ```
564
+ # 服务端使用场景(推荐)
743
565
  gitlab = GitLabLogin(api_key="your_api_key")
744
- user_info, success = await gitlab.login()
566
+ # 1. 获取授权URL
567
+ auth_data, success = await gitlab.get_auth_url()
568
+ if success:
569
+ auth_url = auth_data.get("auth_url")
570
+ state = auth_data.get("state")
571
+ # 将auth_url返回给前端,让用户点击授权
572
+ # 2. 等待登录结果(在服务端轮询)
573
+ user_info, success = await gitlab.wait_for_login(state, timeout=180)
574
+ print(user_info, success)
745
575
  ```
746
576
  """
747
577
 
748
578
  def __init__(self, api_key: str):
749
579
  super().__init__(api_key)
750
580
 
751
- async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
581
+ async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
582
+ """
583
+ description:
584
+ 获取GitLab OAuth授权URL(公共方法,供服务端调用)
585
+
586
+ 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
587
+ 获取到state后,使用wait_for_login方法等待登录结果。
588
+ return:
589
+ auth_data(dict, optional): 授权数据字典,包含auth_url和state
590
+ success(bool): 是否成功
591
+ """
752
592
  data, success = await self._request(
753
593
  "POST", "/api/unified-login/gitlab/auth-url"
754
594
  )
@@ -759,43 +599,20 @@ class GitLabLogin(AsyncClient):
759
599
  return None, False
760
600
  return data, True
761
601
 
762
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
602
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
763
603
  """
764
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,
765
- 终端端轮询等待登录结果
604
+ description:
605
+ 等待GitLab登录结果,轮询检查登录状态(公共方法,供服务端调用)
766
606
 
767
- :param timeout: 等待用户完成授权的超时时间(秒)
607
+ 服务端在用户点击授权后,调用此方法轮询等待登录结果。
608
+ 此方法会持续轮询直到登录成功或超时。
609
+ parameters:
610
+ state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
611
+ timeout(int, optional): 超时时间(秒),默认为180
612
+ return:
613
+ user_info(dict): 用户信息字典
614
+ success(bool): 是否成功
768
615
  """
769
- auth_data, success = await self._get_auth_url()
770
- if not success or not auth_data:
771
- return {}, False
772
-
773
- auth_url = auth_data.get("auth_url")
774
- state = auth_data.get("state") or self._extract_state(auth_url)
775
-
776
- if not auth_url or not state:
777
- return {}, False
778
-
779
- webbrowser.open(auth_url, new=2)
780
-
781
- return await self._wait_for_gitlab_login(state, timeout)
782
-
783
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
784
- if not auth_url:
785
- return None
786
- try:
787
- parsed = urllib.parse.urlparse(auth_url)
788
- query = urllib.parse.parse_qs(parsed.query)
789
- values = query.get("state")
790
- if values:
791
- return values[0]
792
- except Exception:
793
- return None
794
- return None
795
-
796
- async def _wait_for_gitlab_login(
797
- self, state: str, timeout: int
798
- ) -> Tuple[Dict, bool]:
799
616
  interval = 2
800
617
  total_checks = max(1, timeout // interval) if timeout > 0 else 1
801
618
 
@@ -820,7 +637,7 @@ class GitLabLogin(AsyncClient):
820
637
  """
821
638
  description:
822
639
  使用refresh_token刷新access_token
823
-
640
+
824
641
  注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
825
642
  旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
826
643
  parameters:
@@ -884,9 +701,7 @@ class SMSLogin(AsyncClient):
884
701
  )
885
702
  return bool(success)
886
703
 
887
- async def _wait_for_login(
888
- self, phone: str, timeout: int
889
- ) -> Tuple[Dict, bool]:
704
+ async def _wait_for_login(self, phone: str, timeout: int) -> Tuple[Dict, bool]:
890
705
  """
891
706
  等待短信登录结果
892
707
 
@@ -976,9 +791,7 @@ class EmailLogin(AsyncClient):
976
791
  )
977
792
  return bool(success)
978
793
 
979
- async def _wait_for_login(
980
- self, email: str, timeout: int
981
- ) -> Tuple[Dict, bool]:
794
+ async def _wait_for_login(self, email: str, timeout: int) -> Tuple[Dict, bool]:
982
795
  """
983
796
  等待邮箱登录结果
984
797
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraythirdparty
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: PixelArray 第三方微服务客户端
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- pixelarraythirdparty/__init__.py,sha256=nJENJpeEcGI9wm3q8bM__BslEPtmFVRN3-ZqeV_0Bd4,582
1
+ pixelarraythirdparty/__init__.py,sha256=ZPQ3uKaT_Scaf4p_JVcXXb90OdZ01QLEkXh57BGGnuY,582
2
2
  pixelarraythirdparty/client.py,sha256=Ym_IZ6cdoZJoMKW61ZRTfQ81-Hay5dpLTgExoANZwI4,3171
3
3
  pixelarraythirdparty/cron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  pixelarraythirdparty/cron/cron.py,sha256=nv4e2hX_UkEJ-kbEARbInU2J6aREyYZ61dZ-4b9UWJI,3146
@@ -11,11 +11,11 @@ pixelarraythirdparty/product/product.py,sha256=5fgv2Ck860epYXxipY83vePziubCIlocF
11
11
  pixelarraythirdparty/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  pixelarraythirdparty/project/project.py,sha256=a8swjckyn4y3NlIx0-tMbcRwDY9woOijGi7lb1wN7Ok,2455
13
13
  pixelarraythirdparty/unified_login/__init__.py,sha256=tzy3nmRv-qZID-6kYRFarqQVrmkUsEI7D6de_PFu7Tg,327
14
- pixelarraythirdparty/unified_login/unified_login.py,sha256=Klz8Jb-8qIy6Sc1okQS7SQe_ByS0CpO_AvbDnLJnUwM,32700
14
+ pixelarraythirdparty/unified_login/unified_login.py,sha256=chpB8Yj6yoPrh821LRe4BbDlLkK4Kn9n5X6FBiINUp0,29007
15
15
  pixelarraythirdparty/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  pixelarraythirdparty/user/user.py,sha256=SqufSAVMxQElz-NqtlOZs_dG7UtbuE-ygLAyml9oKpY,5761
17
- pixelarraythirdparty-1.2.2.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
18
- pixelarraythirdparty-1.2.2.dist-info/METADATA,sha256=TC042i-Yca5TeDXqu5xereatr-bwINhP_9xLoTpi6qE,993
19
- pixelarraythirdparty-1.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- pixelarraythirdparty-1.2.2.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
21
- pixelarraythirdparty-1.2.2.dist-info/RECORD,,
17
+ pixelarraythirdparty-1.2.4.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
18
+ pixelarraythirdparty-1.2.4.dist-info/METADATA,sha256=2m92kvoPsCInrZsLpS094MxadQzGipcKcYtlEqa6GHY,993
19
+ pixelarraythirdparty-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ pixelarraythirdparty-1.2.4.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
21
+ pixelarraythirdparty-1.2.4.dist-info/RECORD,,