pixelarraythirdparty 1.2.3__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.3"
16
+ __version__ = "1.2.4"
17
17
  __author__ = "Lu qi"
18
18
  __email__ = "qi.lu@pixelarrayai.com"
19
19
 
@@ -9,57 +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
18
  # 服务端使用场景(推荐)
19
- google = OAuth2Login(
19
+ oauth2_login = OAuth2Login(
20
20
  api_key="your_api_key",
21
- provider="google"
21
+ provider="google" # 支持的提供商:google, wechat, github, gitlab, douyin
22
22
  )
23
23
  # 1. 获取授权URL
24
- auth_data, success = await google.get_auth_url()
24
+ auth_data, success = await oauth2_login.get_auth_url()
25
25
  if success:
26
26
  auth_url = auth_data.get("auth_url")
27
27
  state = auth_data.get("state")
28
28
  # 将auth_url返回给前端,让用户点击授权
29
29
  # 2. 等待登录结果(在服务端轮询)
30
- user_info, success = await google.wait_for_login(state, timeout=180)
31
-
32
- # 测试场景(仅用于本地测试,会打开浏览器)
33
- google = OAuth2Login(
34
- api_key="your_api_key",
35
- provider="google"
36
- )
37
- user_info, success = await google.login()
38
-
39
- # 微信PC端扫码登录
40
- wechat = OAuth2Login(
41
- api_key="your_api_key",
42
- provider="wechat",
43
- login_type="desktop"
44
- )
45
- user_info, success = await wechat.login()
46
-
47
- # 微信手机端登录
48
- wechat_mobile = OAuth2Login(
49
- api_key="your_api_key",
50
- provider="wechat",
51
- login_type="mobile"
52
- )
53
- user_info, success = await wechat_mobile.login()
54
-
55
- # 支持refresh_token的提供商(如Google、GitLab)
56
- if user_info.get("refresh_token"):
57
- token_data, success = await google.refresh_access_token(
58
- user_info.get("refresh_token")
59
- )
30
+ user_info, success = await oauth2_login.wait_for_login(state, timeout=180)
31
+ print(user_info, success)
60
32
  ```
61
33
  """
62
-
34
+
63
35
  # 提供商端点映射
64
36
  PROVIDER_ENDPOINTS = {
65
37
  "google": {
@@ -89,13 +61,8 @@ class OAuth2Login(AsyncClient):
89
61
  "wait_login": "/api/unified-login/douyin/wait-login",
90
62
  },
91
63
  }
92
-
93
- def __init__(
94
- self,
95
- api_key: str,
96
- provider: str,
97
- login_type: Optional[str] = None
98
- ):
64
+
65
+ def __init__(self, api_key: str, provider: str, login_type: Optional[str] = None):
99
66
  """
100
67
  description:
101
68
  初始化OAuth2登录客户端
@@ -107,19 +74,19 @@ class OAuth2Login(AsyncClient):
107
74
  super().__init__(api_key)
108
75
  self.provider = provider.lower()
109
76
  self.login_type = login_type
110
-
77
+
111
78
  # 验证提供商是否支持
112
79
  if self.provider not in self.PROVIDER_ENDPOINTS:
113
80
  raise ValueError(
114
81
  f"不支持的提供商: {provider}。"
115
82
  f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
116
83
  )
117
-
84
+
118
85
  # 微信特殊处理:根据login_type选择不同的端点
119
86
  if self.provider == "wechat":
120
87
  if login_type == "mobile":
121
88
  self.provider = "wechat-official"
122
-
89
+
123
90
  def _get_endpoint(self, endpoint_type: str) -> str:
124
91
  """
125
92
  description:
@@ -132,16 +99,14 @@ class OAuth2Login(AsyncClient):
132
99
  endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
133
100
  endpoint = endpoints.get(endpoint_type)
134
101
  if not endpoint:
135
- raise ValueError(
136
- f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
137
- )
102
+ raise ValueError(f"提供商 {self.provider} 不支持 {endpoint_type} 端点")
138
103
  return endpoint
139
-
104
+
140
105
  async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
141
106
  """
142
107
  description:
143
108
  获取OAuth授权URL(公共方法,供服务端调用)
144
-
109
+
145
110
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
146
111
  获取到state后,使用wait_for_login方法等待登录结果。
147
112
  return:
@@ -156,35 +121,12 @@ class OAuth2Login(AsyncClient):
156
121
  if not auth_url:
157
122
  return None, False
158
123
  return data, True
159
-
160
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
161
- """
162
- description:
163
- 从授权URL中提取state参数
164
- parameters:
165
- auth_url(str, optional): 授权URL
166
- return:
167
- state(str, optional): state参数值,如果提取失败则返回None
168
- """
169
- if not auth_url:
170
- return None
171
- try:
172
- parsed = urllib.parse.urlparse(auth_url)
173
- query = urllib.parse.parse_qs(parsed.query)
174
- values = query.get("state")
175
- if values:
176
- return values[0]
177
- except Exception:
178
- return None
179
- return None
180
-
181
- async def wait_for_login(
182
- self, state: str, timeout: int = 180
183
- ) -> Tuple[Dict, bool]:
124
+
125
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
184
126
  """
185
127
  description:
186
128
  等待登录结果,轮询检查登录状态(公共方法,供服务端调用)
187
-
129
+
188
130
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
189
131
  此方法会持续轮询直到登录成功或超时。
190
132
  parameters:
@@ -197,56 +139,29 @@ class OAuth2Login(AsyncClient):
197
139
  interval = 2
198
140
  total_checks = max(1, timeout // interval) if timeout > 0 else 1
199
141
  endpoint = self._get_endpoint("wait_login")
200
-
142
+
201
143
  for _ in range(total_checks):
202
144
  status, response = await self._request_raw(
203
145
  "POST",
204
146
  endpoint,
205
147
  json={"state": state},
206
148
  )
207
-
149
+
208
150
  if status == 200 and response.get("success") is True:
209
151
  return response.get("data", {}), True
210
-
152
+
211
153
  if status in (400, 408):
212
154
  break
213
-
155
+
214
156
  await asyncio.sleep(interval)
215
-
157
+
216
158
  return {}, False
217
-
218
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
219
- """
220
- description:
221
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
222
-
223
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
224
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
225
- parameters:
226
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
227
- return:
228
- user_info(dict): 用户信息字典
229
- success(bool): 是否成功
230
- """
231
- auth_data, success = await self.get_auth_url()
232
- if not success or not auth_data:
233
- return {}, False
234
-
235
- auth_url = auth_data.get("auth_url")
236
- state = auth_data.get("state") or self._extract_state(auth_url)
237
-
238
- if not auth_url or not state:
239
- return {}, False
240
-
241
- webbrowser.open(auth_url, new=2)
242
-
243
- return await self.wait_for_login(state, timeout)
244
-
159
+
245
160
  async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
246
161
  """
247
162
  description:
248
163
  使用refresh_token刷新access_token(仅支持Google和GitLab)
249
-
164
+
250
165
  注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
251
166
  旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
252
167
  Google 也可能在某些情况下返回新的 refresh_token。
@@ -260,7 +175,7 @@ class OAuth2Login(AsyncClient):
260
175
  endpoint = self._get_endpoint("refresh_token")
261
176
  except ValueError:
262
177
  return {}, False
263
-
178
+
264
179
  data, success = await self._request(
265
180
  "POST",
266
181
  endpoint,
@@ -290,16 +205,6 @@ class GoogleLogin(AsyncClient):
290
205
  if success:
291
206
  access_token = user_info.get("access_token")
292
207
  refresh_token = user_info.get("refresh_token")
293
-
294
- # 使用refresh_token刷新access_token
295
- if refresh_token:
296
- token_data, success = await google.refresh_access_token(refresh_token)
297
- if success:
298
- new_access_token = token_data.get("access_token")
299
-
300
- # 测试场景(仅用于本地测试,会打开浏览器)
301
- google = GoogleLogin(api_key="your_api_key")
302
- user_info, success = await google.login()
303
208
  ```
304
209
  """
305
210
 
@@ -310,7 +215,7 @@ class GoogleLogin(AsyncClient):
310
215
  """
311
216
  description:
312
217
  获取Google OAuth授权URL(公共方法,供服务端调用)
313
-
218
+
314
219
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
315
220
  获取到state后,使用wait_for_login方法等待登录结果。
316
221
  return:
@@ -327,53 +232,11 @@ class GoogleLogin(AsyncClient):
327
232
  return None, False
328
233
  return data, True
329
234
 
330
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
331
- """
332
- description:
333
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
334
-
335
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
336
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
337
- parameters:
338
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
339
- return:
340
- user_info(dict): 用户信息字典
341
- success(bool): 是否成功
342
- """
343
- auth_data, success = await self.get_auth_url()
344
- if not success or not auth_data:
345
- return {}, False
346
-
347
- auth_url = auth_data.get("auth_url")
348
- state = auth_data.get("state") or self._extract_state(auth_url)
349
-
350
- if not auth_url or not state:
351
- return {}, False
352
-
353
- webbrowser.open(auth_url, new=2)
354
-
355
- return await self.wait_for_login(state, timeout)
356
-
357
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
358
- if not auth_url:
359
- return None
360
- try:
361
- parsed = urllib.parse.urlparse(auth_url)
362
- query = urllib.parse.parse_qs(parsed.query)
363
- values = query.get("state")
364
- if values:
365
- return values[0]
366
- except Exception:
367
- return None
368
- return None
369
-
370
- async def wait_for_login(
371
- self, state: str, timeout: int = 180
372
- ) -> Tuple[Dict, bool]:
235
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
373
236
  """
374
237
  description:
375
238
  等待Google登录结果,轮询检查登录状态(公共方法,供服务端调用)
376
-
239
+
377
240
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
378
241
  此方法会持续轮询直到登录成功或超时。
379
242
  parameters:
@@ -426,7 +289,7 @@ class GoogleLogin(AsyncClient):
426
289
  class WechatLogin(AsyncClient):
427
290
  """
428
291
  微信 OAuth2 登录客户端
429
-
292
+
430
293
  支持两种登录方式:
431
294
  - desktop: PC端扫码登录(使用微信开放平台)
432
295
  - mobile: 微信公众号OAuth登录(手机端微信内打开)
@@ -443,7 +306,8 @@ class WechatLogin(AsyncClient):
443
306
  # 将auth_url返回给前端,让用户扫码授权
444
307
  # 等待登录结果
445
308
  user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="desktop")
446
-
309
+ print(user_info, success)
310
+
447
311
  # 微信公众号登录(手机端)
448
312
  auth_data, success = await wechat.get_auth_url(login_type="mobile")
449
313
  if success:
@@ -452,21 +316,20 @@ class WechatLogin(AsyncClient):
452
316
  # 将auth_url返回给前端,让用户在微信内打开授权
453
317
  # 等待登录结果
454
318
  user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="mobile")
455
-
456
- # 测试场景(仅用于本地测试,会打开浏览器)
457
- wechat = WechatLogin(api_key="your_api_key")
458
- user_info, success = await wechat.login(login_type="desktop")
319
+ print(user_info, success)
459
320
  ```
460
321
  """
461
322
 
462
323
  def __init__(self, api_key: str):
463
324
  super().__init__(api_key)
464
325
 
465
- 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]:
466
329
  """
467
330
  description:
468
331
  获取微信授权URL(公共方法,供服务端调用)
469
-
332
+
470
333
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
471
334
  获取到state后,使用wait_for_login方法等待登录结果。
472
335
  parameters:
@@ -481,7 +344,7 @@ class WechatLogin(AsyncClient):
481
344
  else:
482
345
  # PC端扫码登录
483
346
  endpoint = "/api/unified-login/wechat/auth-url"
484
-
347
+
485
348
  data, success = await self._request("POST", endpoint)
486
349
  if not success:
487
350
  return None, False
@@ -490,62 +353,13 @@ class WechatLogin(AsyncClient):
490
353
  return None, False
491
354
  return data, True
492
355
 
493
- async def login(self, login_type: str = "desktop", timeout: int = 180) -> Tuple[Dict, bool]:
494
- """
495
- description:
496
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
497
-
498
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
499
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
500
- parameters:
501
- login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
502
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
503
- return:
504
- user_info(dict): 用户信息字典
505
- success(bool): 是否成功
506
- """
507
- auth_data, success = await self.get_auth_url(login_type)
508
- if not success or not auth_data:
509
- return {}, False
510
-
511
- auth_url = auth_data.get("auth_url")
512
- state = auth_data.get("state") or self._extract_state(auth_url)
513
-
514
- if not auth_url or not state:
515
- return {}, False
516
-
517
- webbrowser.open(auth_url, new=2)
518
-
519
- return await self.wait_for_login(state, timeout, login_type)
520
-
521
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
522
- """
523
- description:
524
- 从授权URL中提取state参数
525
- parameters:
526
- auth_url(str, optional): 授权URL
527
- return:
528
- state(str, optional): state参数值,如果提取失败则返回None
529
- """
530
- if not auth_url:
531
- return None
532
- try:
533
- parsed = urllib.parse.urlparse(auth_url)
534
- query = urllib.parse.parse_qs(parsed.query)
535
- values = query.get("state")
536
- if values:
537
- return values[0]
538
- except Exception:
539
- return None
540
- return None
541
-
542
356
  async def wait_for_login(
543
357
  self, state: str, timeout: int = 180, login_type: str = "desktop"
544
358
  ) -> Tuple[Dict, bool]:
545
359
  """
546
360
  description:
547
361
  等待微信登录结果,轮询检查登录状态(公共方法,供服务端调用)
548
-
362
+
549
363
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
550
364
  此方法会持续轮询直到登录成功或超时。
551
365
  parameters:
@@ -599,10 +413,7 @@ class GitHubLogin(AsyncClient):
599
413
  # 将auth_url返回给前端,让用户点击授权
600
414
  # 2. 等待登录结果(在服务端轮询)
601
415
  user_info, success = await github.wait_for_login(state, timeout=180)
602
-
603
- # 测试场景(仅用于本地测试,会打开浏览器)
604
- github = GitHubLogin(api_key="your_api_key")
605
- user_info, success = await github.login()
416
+ print(user_info, success)
606
417
  ```
607
418
  """
608
419
 
@@ -613,7 +424,7 @@ class GitHubLogin(AsyncClient):
613
424
  """
614
425
  description:
615
426
  获取GitHub OAuth授权URL(公共方法,供服务端调用)
616
-
427
+
617
428
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
618
429
  获取到state后,使用wait_for_login方法等待登录结果。
619
430
  return:
@@ -630,61 +441,11 @@ class GitHubLogin(AsyncClient):
630
441
  return None, False
631
442
  return data, True
632
443
 
633
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
634
- """
635
- description:
636
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
637
-
638
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
639
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
640
- parameters:
641
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
642
- return:
643
- user_info(dict): 用户信息字典
644
- success(bool): 是否成功
645
- """
646
- auth_data, success = await self.get_auth_url()
647
- if not success or not auth_data:
648
- return {}, False
649
-
650
- auth_url = auth_data.get("auth_url")
651
- state = auth_data.get("state") or self._extract_state(auth_url)
652
-
653
- if not auth_url or not state:
654
- return {}, False
655
-
656
- webbrowser.open(auth_url, new=2)
657
-
658
- return await self.wait_for_login(state, timeout)
659
-
660
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
661
- """
662
- description:
663
- 从授权URL中提取state参数
664
- parameters:
665
- auth_url(str, optional): 授权URL
666
- return:
667
- state(str, optional): state参数值,如果提取失败则返回None
668
- """
669
- if not auth_url:
670
- return None
671
- try:
672
- parsed = urllib.parse.urlparse(auth_url)
673
- query = urllib.parse.parse_qs(parsed.query)
674
- values = query.get("state")
675
- if values:
676
- return values[0]
677
- except Exception:
678
- return None
679
- return None
680
-
681
- async def wait_for_login(
682
- self, state: str, timeout: int = 180
683
- ) -> Tuple[Dict, bool]:
444
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
684
445
  """
685
446
  description:
686
447
  等待GitHub登录结果,轮询检查登录状态(公共方法,供服务端调用)
687
-
448
+
688
449
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
689
450
  此方法会持续轮询直到登录成功或超时。
690
451
  parameters:
@@ -731,10 +492,7 @@ class DouyinLogin(AsyncClient):
731
492
  # 将auth_url返回给前端,让用户点击授权
732
493
  # 2. 等待登录结果(在服务端轮询)
733
494
  user_info, success = await douyin.wait_for_login(state, timeout=180)
734
-
735
- # 测试场景(仅用于本地测试,会打开浏览器)
736
- douyin = DouyinLogin(api_key="your_api_key")
737
- user_info, success = await douyin.login()
495
+ print(user_info, success)
738
496
  ```
739
497
  """
740
498
 
@@ -745,7 +503,7 @@ class DouyinLogin(AsyncClient):
745
503
  """
746
504
  description:
747
505
  获取抖音OAuth授权URL(公共方法,供服务端调用)
748
-
506
+
749
507
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
750
508
  获取到state后,使用wait_for_login方法等待登录结果。
751
509
  return:
@@ -762,61 +520,11 @@ class DouyinLogin(AsyncClient):
762
520
  return None, False
763
521
  return data, True
764
522
 
765
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
766
- """
767
- description:
768
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
769
-
770
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
771
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
772
- parameters:
773
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
774
- return:
775
- user_info(dict): 用户信息字典
776
- success(bool): 是否成功
777
- """
778
- auth_data, success = await self.get_auth_url()
779
- if not success or not auth_data:
780
- return {}, False
781
-
782
- auth_url = auth_data.get("auth_url")
783
- state = auth_data.get("state") or self._extract_state(auth_url)
784
-
785
- if not auth_url or not state:
786
- return {}, False
787
-
788
- webbrowser.open(auth_url, new=2)
789
-
790
- return await self.wait_for_login(state, timeout)
791
-
792
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
793
- """
794
- description:
795
- 从授权URL中提取state参数
796
- parameters:
797
- auth_url(str, optional): 授权URL
798
- return:
799
- state(str, optional): state参数值,如果提取失败则返回None
800
- """
801
- if not auth_url:
802
- return None
803
- try:
804
- parsed = urllib.parse.urlparse(auth_url)
805
- query = urllib.parse.parse_qs(parsed.query)
806
- values = query.get("state")
807
- if values:
808
- return values[0]
809
- except Exception:
810
- return None
811
- return None
812
-
813
- async def wait_for_login(
814
- self, state: str, timeout: int = 180
815
- ) -> Tuple[Dict, bool]:
523
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
816
524
  """
817
525
  description:
818
526
  等待抖音登录结果,轮询检查登录状态(公共方法,供服务端调用)
819
-
527
+
820
528
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
821
529
  此方法会持续轮询直到登录成功或超时。
822
530
  parameters:
@@ -863,13 +571,7 @@ class GitLabLogin(AsyncClient):
863
571
  # 将auth_url返回给前端,让用户点击授权
864
572
  # 2. 等待登录结果(在服务端轮询)
865
573
  user_info, success = await gitlab.wait_for_login(state, timeout=180)
866
- if success and user_info.get("refresh_token"):
867
- # 使用refresh_token刷新access_token
868
- token_data, success = await gitlab.refresh_access_token(user_info.get("refresh_token"))
869
-
870
- # 测试场景(仅用于本地测试,会打开浏览器)
871
- gitlab = GitLabLogin(api_key="your_api_key")
872
- user_info, success = await gitlab.login()
574
+ print(user_info, success)
873
575
  ```
874
576
  """
875
577
 
@@ -880,7 +582,7 @@ class GitLabLogin(AsyncClient):
880
582
  """
881
583
  description:
882
584
  获取GitLab OAuth授权URL(公共方法,供服务端调用)
883
-
585
+
884
586
  服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
885
587
  获取到state后,使用wait_for_login方法等待登录结果。
886
588
  return:
@@ -897,53 +599,11 @@ class GitLabLogin(AsyncClient):
897
599
  return None, False
898
600
  return data, True
899
601
 
900
- async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
901
- """
902
- description:
903
- 仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
904
-
905
- 注意:此方法仅用于本地测试场景,会自动打开浏览器。
906
- 在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
907
- parameters:
908
- timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
909
- return:
910
- user_info(dict): 用户信息字典
911
- success(bool): 是否成功
912
- """
913
- auth_data, success = await self.get_auth_url()
914
- if not success or not auth_data:
915
- return {}, False
916
-
917
- auth_url = auth_data.get("auth_url")
918
- state = auth_data.get("state") or self._extract_state(auth_url)
919
-
920
- if not auth_url or not state:
921
- return {}, False
922
-
923
- webbrowser.open(auth_url, new=2)
924
-
925
- return await self.wait_for_login(state, timeout)
926
-
927
- def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
928
- if not auth_url:
929
- return None
930
- try:
931
- parsed = urllib.parse.urlparse(auth_url)
932
- query = urllib.parse.parse_qs(parsed.query)
933
- values = query.get("state")
934
- if values:
935
- return values[0]
936
- except Exception:
937
- return None
938
- return None
939
-
940
- async def wait_for_login(
941
- self, state: str, timeout: int = 180
942
- ) -> Tuple[Dict, bool]:
602
+ async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
943
603
  """
944
604
  description:
945
605
  等待GitLab登录结果,轮询检查登录状态(公共方法,供服务端调用)
946
-
606
+
947
607
  服务端在用户点击授权后,调用此方法轮询等待登录结果。
948
608
  此方法会持续轮询直到登录成功或超时。
949
609
  parameters:
@@ -977,7 +637,7 @@ class GitLabLogin(AsyncClient):
977
637
  """
978
638
  description:
979
639
  使用refresh_token刷新access_token
980
-
640
+
981
641
  注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
982
642
  旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
983
643
  parameters:
@@ -1041,9 +701,7 @@ class SMSLogin(AsyncClient):
1041
701
  )
1042
702
  return bool(success)
1043
703
 
1044
- async def _wait_for_login(
1045
- self, phone: str, timeout: int
1046
- ) -> Tuple[Dict, bool]:
704
+ async def _wait_for_login(self, phone: str, timeout: int) -> Tuple[Dict, bool]:
1047
705
  """
1048
706
  等待短信登录结果
1049
707
 
@@ -1133,9 +791,7 @@ class EmailLogin(AsyncClient):
1133
791
  )
1134
792
  return bool(success)
1135
793
 
1136
- async def _wait_for_login(
1137
- self, email: str, timeout: int
1138
- ) -> Tuple[Dict, bool]:
794
+ async def _wait_for_login(self, email: str, timeout: int) -> Tuple[Dict, bool]:
1139
795
  """
1140
796
  等待邮箱登录结果
1141
797
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraythirdparty
3
- Version: 1.2.3
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=zQsr327IH9uZgfbsXIpwvP6B_KJltOQFfaWJ5NqGHAo,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=A1Sl0g0s9MrBdSmrFiui2r15sA0Xd17iTQIUfJsZEFQ,41483
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.3.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
18
- pixelarraythirdparty-1.2.3.dist-info/METADATA,sha256=_TNjpH-NiF7st_CYAxM9Gz8LWs01m-Prlzjw7vZ4708,993
19
- pixelarraythirdparty-1.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- pixelarraythirdparty-1.2.3.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
21
- pixelarraythirdparty-1.2.3.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,,