pixelarraythirdparty 1.0.9__tar.gz → 1.1.0__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.
Files changed (24) hide show
  1. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/PKG-INFO +1 -1
  2. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/__init__.py +1 -3
  3. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty.egg-info/PKG-INFO +1 -1
  4. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty.egg-info/SOURCES.txt +0 -2
  5. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pyproject.toml +1 -1
  6. pixelarraythirdparty-1.0.9/pixelarraythirdparty/filestorage/__init__.py +0 -6
  7. pixelarraythirdparty-1.0.9/pixelarraythirdparty/filestorage/filestorage.py +0 -530
  8. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/LICENSE +0 -0
  9. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/MANIFEST.in +0 -0
  10. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/client.py +0 -0
  11. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/cron/__init__.py +0 -0
  12. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/cron/cron.py +0 -0
  13. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/order/__init__.py +0 -0
  14. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/order/order.py +0 -0
  15. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/product/__init__.py +0 -0
  16. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/product/product.py +0 -0
  17. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/unified_login/__init__.py +0 -0
  18. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/unified_login/unified_login.py +0 -0
  19. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/user/__init__.py +0 -0
  20. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty/user/user.py +0 -0
  21. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty.egg-info/dependency_links.txt +0 -0
  22. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty.egg-info/requires.txt +0 -0
  23. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/pixelarraythirdparty.egg-info/top_level.txt +0 -0
  24. {pixelarraythirdparty-1.0.9 → pixelarraythirdparty-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraythirdparty
3
- Version: 1.0.9
3
+ Version: 1.1.0
4
4
  Summary: PixelArray 第三方微服务客户端
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -11,7 +11,7 @@ PixelArray 第三方微服务客户端
11
11
  - unified_login: 统一登录模块
12
12
  """
13
13
 
14
- __version__ = "1.0.9"
14
+ __version__ = "1.1.0"
15
15
  __author__ = "Lu qi"
16
16
  __email__ = "qi.lu@pixelarrayai.com"
17
17
 
@@ -21,6 +21,4 @@ __all__ = [
21
21
  "cron",
22
22
  "user",
23
23
  "order",
24
- "filestorage",
25
- "unified_login",
26
24
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraythirdparty
3
- Version: 1.0.9
3
+ Version: 1.1.0
4
4
  Summary: PixelArray 第三方微服务客户端
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -10,8 +10,6 @@ pixelarraythirdparty.egg-info/requires.txt
10
10
  pixelarraythirdparty.egg-info/top_level.txt
11
11
  pixelarraythirdparty/cron/__init__.py
12
12
  pixelarraythirdparty/cron/cron.py
13
- pixelarraythirdparty/filestorage/__init__.py
14
- pixelarraythirdparty/filestorage/filestorage.py
15
13
  pixelarraythirdparty/order/__init__.py
16
14
  pixelarraythirdparty/order/order.py
17
15
  pixelarraythirdparty/product/__init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pixelarraythirdparty"
7
- version = "1.0.9"
7
+ version = "1.1.0"
8
8
  authors = [
9
9
  {name = "Lu qi", email = "qi.lu@pixelarrayai.com"},
10
10
  ]
@@ -1,6 +0,0 @@
1
- from pixelarraythirdparty.filestorage.filestorage import (
2
- FileStorageManagerAsync,
3
- )
4
-
5
- __all__ = ["FileStorageManagerAsync"]
6
-
@@ -1,530 +0,0 @@
1
- from pixelarraythirdparty.client import AsyncClient
2
- from typing import Dict, Any, Optional, List, Tuple, AsyncGenerator
3
- import os
4
- import aiohttp
5
- import mimetypes
6
- import math
7
- import time
8
-
9
-
10
- class FileStorageManagerAsync(AsyncClient):
11
- async def upload(
12
- self, file_path: str, parent_id: Optional[int] = None
13
- ) -> Tuple[Dict[str, Any], bool]:
14
- """
15
- description:
16
- 上传文件(合并了初始化、分片上传、完成上传三个步骤)
17
- parameters:
18
- file_path: 文件路径(str)
19
- parent_id: 父文件夹ID(可选)
20
- return:
21
- - data: 结果数据
22
- - success: 是否成功
23
- """
24
- final_result: Dict[str, Any] = {}
25
- final_success = False
26
- async for progress in self.upload_stream(file_path, parent_id):
27
- event = progress.get("event")
28
- if event == "error":
29
- return {}, False
30
- if event == "complete" and progress.get("success"):
31
- final_result = progress.get("result", {})
32
- final_success = True
33
- return final_result, final_success
34
-
35
- async def upload_stream(
36
- self, file_path: str, parent_id: Optional[int] = None
37
- ) -> AsyncGenerator[Dict[str, Any], None]:
38
- """
39
- description:
40
- 上传文件(流式,返回生成器,包含进度信息)
41
- """
42
- chunk_size = 2 * 1024 * 1024 # 2MB
43
- upload_start = time.perf_counter()
44
- with open(file_path, "rb") as f:
45
- file_bytes = f.read()
46
-
47
- total_size = len(file_bytes)
48
- file_name = os.path.basename(file_path)
49
- mime_type = mimetypes.guess_type(file_path)[0]
50
- init_data = {
51
- "filename": file_name,
52
- "file_type": mime_type,
53
- "total_size": total_size,
54
- }
55
- if parent_id is not None:
56
- init_data["parent_id"] = parent_id
57
-
58
- init_result, success = await self._request(
59
- "POST", "/api/file_storage/upload/init", json=init_data
60
- )
61
- if not success:
62
- yield {
63
- "event": "error",
64
- "percentage": 0,
65
- "total_chunks": 0,
66
- "remaining_chunks": 0,
67
- "total_bytes": total_size,
68
- "processed_bytes": 0,
69
- "speed": 0,
70
- "message": "初始化上传失败",
71
- "success": False,
72
- }
73
- return
74
-
75
- upload_id = init_result.get("upload_id")
76
- chunk_urls = init_result.get("chunk_urls", [])
77
- total_chunks = len(chunk_urls)
78
-
79
- if not upload_id or not chunk_urls:
80
- yield {
81
- "event": "error",
82
- "percentage": 0,
83
- "total_chunks": 0,
84
- "remaining_chunks": 0,
85
- "total_bytes": total_size,
86
- "processed_bytes": 0,
87
- "speed": 0,
88
- "message": "缺少上传ID或分片信息",
89
- "success": False,
90
- }
91
- return
92
-
93
- yield {
94
- "event": "init",
95
- "percentage": 0,
96
- "total_chunks": total_chunks,
97
- "remaining_chunks": total_chunks,
98
- "total_bytes": total_size,
99
- "processed_bytes": 0,
100
- "speed": 0,
101
- "message": "初始化完成,开始上传分片",
102
- "success": True,
103
- }
104
-
105
- parts: List[Dict[str, Any]] = []
106
- uploaded_bytes = 0
107
-
108
- async with aiohttp.ClientSession() as session:
109
- for idx, chunk_info in enumerate(chunk_urls):
110
- part_number = chunk_info.get("part_number")
111
- url = chunk_info.get("url")
112
- start = idx * chunk_size
113
- end = min(start + chunk_size, total_size)
114
- chunk_data = file_bytes[start:end]
115
-
116
- if not url or not part_number:
117
- percentage = (
118
- 0
119
- if total_size == 0
120
- else min((uploaded_bytes / total_size) * 100, 100)
121
- )
122
- yield {
123
- "event": "error",
124
- "percentage": percentage,
125
- "total_chunks": total_chunks,
126
- "remaining_chunks": total_chunks - idx,
127
- "total_bytes": total_size,
128
- "processed_bytes": uploaded_bytes,
129
- "speed": 0,
130
- "message": "分片信息缺失",
131
- "success": False,
132
- }
133
- return
134
-
135
- chunk_start = time.perf_counter()
136
- try:
137
- async with session.put(url, data=chunk_data) as resp:
138
- if resp.status != 200:
139
- raise RuntimeError(
140
- f"分片上传失败,状态码:{resp.status}"
141
- )
142
- etag = resp.headers.get("ETag", "").strip('"')
143
- parts.append(
144
- {
145
- "part_number": part_number,
146
- "etag": etag,
147
- }
148
- )
149
- except Exception as exc:
150
- percentage = (
151
- 0
152
- if total_size == 0
153
- else min((uploaded_bytes / total_size) * 100, 100)
154
- )
155
- yield {
156
- "event": "error",
157
- "percentage": percentage,
158
- "total_chunks": total_chunks,
159
- "remaining_chunks": max(total_chunks - idx, 0),
160
- "total_bytes": total_size,
161
- "processed_bytes": uploaded_bytes,
162
- "speed": 0,
163
- "message": f"分片上传异常:{exc}",
164
- "success": False,
165
- }
166
- return
167
-
168
- uploaded_bytes += len(chunk_data)
169
- duration = max(time.perf_counter() - chunk_start, 1e-6)
170
- speed = len(chunk_data) / duration
171
- percentage = (
172
- 100
173
- if total_size == 0
174
- else min((uploaded_bytes / total_size) * 100, 100)
175
- )
176
-
177
- yield {
178
- "event": "chunk",
179
- "percentage": percentage,
180
- "total_chunks": total_chunks,
181
- "remaining_chunks": max(total_chunks - (idx + 1), 0),
182
- "total_bytes": total_size,
183
- "processed_bytes": uploaded_bytes,
184
- "chunk_index": idx,
185
- "chunk_size": len(chunk_data),
186
- "speed": speed,
187
- "message": f"分片{idx + 1}/{total_chunks}上传完成",
188
- "success": True,
189
- }
190
-
191
- complete_data = {
192
- "upload_id": upload_id,
193
- "parts": parts,
194
- }
195
- complete_result, success = await self._request(
196
- "POST", "/api/file_storage/upload/complete", json=complete_data
197
- )
198
- if not success:
199
- yield {
200
- "event": "error",
201
- "percentage": 100,
202
- "total_chunks": total_chunks,
203
- "remaining_chunks": 0,
204
- "total_bytes": total_size,
205
- "processed_bytes": total_size,
206
- "speed": 0,
207
- "message": "完成上传失败",
208
- "success": False,
209
- }
210
- return
211
-
212
- total_duration = max(time.perf_counter() - upload_start, 1e-6)
213
- yield {
214
- "event": "complete",
215
- "percentage": 100,
216
- "total_chunks": total_chunks,
217
- "remaining_chunks": 0,
218
- "total_bytes": total_size,
219
- "processed_bytes": total_size,
220
- "speed": total_size / total_duration if total_duration else 0,
221
- "message": "上传完成",
222
- "success": True,
223
- "result": complete_result,
224
- }
225
-
226
- async def list_files(
227
- self,
228
- parent_id: Optional[int] = None,
229
- is_folder: Optional[bool] = None,
230
- page: int = 1,
231
- page_size: int = 50,
232
- ) -> Tuple[List[Dict[str, Any]], bool]:
233
- """
234
- description:
235
- 获取文件列表
236
- parameters:
237
- parent_id: 父文件夹ID(可选)
238
- is_folder: 是否只查询文件夹(可选)
239
- page: 页码(可选)
240
- page_size: 每页数量(可选)
241
- return:
242
- - data: 文件列表数据
243
- - success: 是否成功
244
- """
245
- data = {
246
- "page": page,
247
- "page_size": page_size,
248
- }
249
- if parent_id is not None:
250
- data["parent_id"] = parent_id
251
- if is_folder is not None:
252
- data["is_folder"] = is_folder
253
-
254
- result, success = await self._request(
255
- "POST", "/api/file_storage/files/list", json=data
256
- )
257
- if not success:
258
- return {}, False
259
- return result, True
260
-
261
- async def create_folder(
262
- self,
263
- folder_name: str,
264
- parent_id: Optional[int] = None,
265
- ) -> Tuple[Dict[str, Any], bool]:
266
- """
267
- description:
268
- 创建文件夹
269
- parameters:
270
- folder_name: 文件夹名称
271
- parent_id: 父文件夹ID(可选)
272
- return:
273
- - data: 文件夹数据
274
- - success: 是否成功
275
- """
276
- data = {
277
- "folder_name": folder_name,
278
- }
279
- if parent_id is not None:
280
- data["parent_id"] = parent_id
281
-
282
- data, success = await self._request(
283
- "POST", "/api/file_storage/files/folder/create", json=data
284
- )
285
- if not success:
286
- return {}, False
287
- return data, True
288
-
289
- async def delete_file(
290
- self,
291
- record_id: int,
292
- ) -> Tuple[Dict[str, Any], bool]:
293
- """
294
- description:
295
- 删除文件或文件夹
296
- parameters:
297
- record_id: 文件或文件夹ID
298
- return:
299
- - data: 结果数据
300
- - success: 是否成功
301
- """
302
- data, success = await self._request(
303
- "DELETE", f"/api/file_storage/files/{record_id}"
304
- )
305
- if not success:
306
- return {}, False
307
- return data, True
308
-
309
- async def get_folder_path(
310
- self,
311
- record_id: int,
312
- ) -> Tuple[List[Dict[str, Any]], bool]:
313
- """
314
- description:
315
- 获取文件夹的完整路径
316
- parameters:
317
- record_id: 文件夹ID
318
- return:
319
- - data: 文件夹路径列表
320
- - success: 是否成功
321
- """
322
- data, success = await self._request(
323
- "GET", f"/api/file_storage/files/{record_id}/path"
324
- )
325
- if not success:
326
- return [], False
327
- # 如果data是字典,尝试获取data字段(因为API返回的是{"data": [...]})
328
- if isinstance(data, dict):
329
- path_list = data.get("data", [])
330
- if isinstance(path_list, list):
331
- return path_list, True
332
- # 如果data本身就是列表,直接返回
333
- if isinstance(data, list):
334
- return data, True
335
- return [], False
336
-
337
- async def generate_signed_url(
338
- self,
339
- record_id: int,
340
- expires: int = 3600,
341
- ) -> Tuple[Dict[str, Any], bool]:
342
- """
343
- 生成签名URL(异步版本)
344
- """
345
- data = {
346
- "expires": expires,
347
- }
348
- data, success = await self._request(
349
- "POST", f"/api/file_storage/files/{record_id}/generate_url", json=data
350
- )
351
- if not success:
352
- return {}, False
353
- return data, True
354
-
355
- async def download(
356
- self,
357
- record_id: int,
358
- save_path: str,
359
- ) -> Tuple[Dict[str, Any], bool]:
360
- """
361
- description:
362
- 下载文件
363
- parameters:
364
- record_id: 文件记录ID
365
- save_path: 保存路径
366
- return:
367
- - data: 下载结果数据
368
- - success: 是否成功
369
- """
370
- final_result: Dict[str, Any] = {}
371
- final_success = False
372
- async for progress in self.download_stream(record_id, save_path):
373
- event = progress.get("event")
374
- if event == "error":
375
- return {}, False
376
- if event == "complete" and progress.get("success"):
377
- final_result = progress.get("result", {})
378
- final_success = True
379
- return final_result, final_success
380
-
381
- async def download_stream(
382
- self,
383
- record_id: int,
384
- save_path: str,
385
- ) -> AsyncGenerator[Dict[str, Any], None]:
386
- """
387
- description:
388
- 下载文件(流式,返回生成器)
389
- """
390
- chunk_size = 2 * 1024 * 1024
391
- signed_url_data, success = await self.generate_signed_url(record_id)
392
- if not success:
393
- yield {
394
- "event": "error",
395
- "percentage": 0,
396
- "total_chunks": 0,
397
- "remaining_chunks": 0,
398
- "total_bytes": 0,
399
- "processed_bytes": 0,
400
- "speed": 0,
401
- "message": "生成签名URL失败",
402
- "success": False,
403
- }
404
- return
405
-
406
- signed_url = signed_url_data.get("signed_url")
407
- file_record = signed_url_data.get("file_record", {}) or {}
408
- total_size = file_record.get("file_size", 0) or 0
409
-
410
- if not signed_url:
411
- yield {
412
- "event": "error",
413
- "percentage": 0,
414
- "total_chunks": 0,
415
- "remaining_chunks": 0,
416
- "total_bytes": total_size,
417
- "processed_bytes": 0,
418
- "speed": 0,
419
- "message": "签名URL为空",
420
- "success": False,
421
- }
422
- return
423
-
424
- total_chunks = math.ceil(total_size / chunk_size) if total_size else 0
425
-
426
- yield {
427
- "event": "init",
428
- "percentage": 0,
429
- "total_chunks": total_chunks,
430
- "remaining_chunks": total_chunks,
431
- "total_bytes": total_size,
432
- "processed_bytes": 0,
433
- "speed": 0,
434
- "message": "开始下载文件",
435
- "success": True,
436
- }
437
-
438
- os.makedirs(os.path.dirname(save_path) or ".", exist_ok=True)
439
-
440
- downloaded_bytes = 0
441
- chunk_index = 0
442
- download_start = time.perf_counter()
443
-
444
- async with aiohttp.ClientSession() as session:
445
- try:
446
- async with session.get(signed_url) as resp:
447
- if resp.status != 200:
448
- raise RuntimeError(f"文件下载失败,状态码:{resp.status}")
449
-
450
- header_size = resp.headers.get("Content-Length")
451
- if total_size == 0 and header_size:
452
- try:
453
- total_size = int(header_size)
454
- total_chunks = (
455
- math.ceil(total_size / chunk_size)
456
- if total_size
457
- else 0
458
- )
459
- except ValueError:
460
- total_size = 0
461
-
462
- with open(save_path, "wb") as f:
463
- async for chunk in resp.content.iter_chunked(chunk_size):
464
- chunk_start = time.perf_counter()
465
- chunk_index += 1
466
- downloaded_bytes += len(chunk)
467
- f.write(chunk)
468
-
469
- chunk_duration = max(
470
- time.perf_counter() - chunk_start, 1e-6
471
- )
472
- instant_speed = len(chunk) / chunk_duration
473
- percentage = (
474
- 0
475
- if total_size == 0
476
- else min(
477
- (downloaded_bytes / total_size) * 100, 100
478
- )
479
- )
480
- remaining = (
481
- max(total_chunks - chunk_index, 0)
482
- if total_chunks
483
- else 0
484
- )
485
-
486
- yield {
487
- "event": "chunk",
488
- "percentage": percentage,
489
- "total_chunks": total_chunks,
490
- "remaining_chunks": remaining,
491
- "total_bytes": total_size,
492
- "processed_bytes": downloaded_bytes,
493
- "chunk_index": chunk_index - 1,
494
- "chunk_size": len(chunk),
495
- "speed": instant_speed,
496
- "message": f"分片{chunk_index}/{total_chunks or '?'}下载完成",
497
- "success": True,
498
- }
499
- except Exception as exc:
500
- yield {
501
- "event": "error",
502
- "percentage": 0,
503
- "total_chunks": total_chunks,
504
- "remaining_chunks": total_chunks,
505
- "total_bytes": total_size,
506
- "processed_bytes": downloaded_bytes,
507
- "speed": 0,
508
- "message": f"下载过程中发生错误:{exc}",
509
- "success": False,
510
- }
511
- return
512
-
513
- total_duration = max(time.perf_counter() - download_start, 1e-6)
514
- result = {
515
- "total_size": total_size,
516
- "success": True,
517
- }
518
- yield {
519
- "event": "complete",
520
- "percentage": 100,
521
- "total_chunks": total_chunks,
522
- "remaining_chunks": 0,
523
- "total_bytes": total_size,
524
- "processed_bytes": total_size if total_size else downloaded_bytes,
525
- "speed": (total_size or downloaded_bytes)
526
- / max(total_duration, 1e-6),
527
- "message": "下载完成",
528
- "success": True,
529
- "result": result,
530
- }