lingxingapi 1.0.0__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.

Potentially problematic release.


This version of lingxingapi might be problematic. Click here for more details.

Files changed (65) hide show
  1. lingxingapi/__init__.py +7 -0
  2. lingxingapi/ads/__init__.py +0 -0
  3. lingxingapi/ads/api.py +5946 -0
  4. lingxingapi/ads/param.py +192 -0
  5. lingxingapi/ads/route.py +134 -0
  6. lingxingapi/ads/schema.py +2615 -0
  7. lingxingapi/api.py +443 -0
  8. lingxingapi/base/__init__.py +0 -0
  9. lingxingapi/base/api.py +409 -0
  10. lingxingapi/base/param.py +59 -0
  11. lingxingapi/base/route.py +11 -0
  12. lingxingapi/base/schema.py +198 -0
  13. lingxingapi/basic/__init__.py +0 -0
  14. lingxingapi/basic/api.py +466 -0
  15. lingxingapi/basic/param.py +72 -0
  16. lingxingapi/basic/route.py +20 -0
  17. lingxingapi/basic/schema.py +212 -0
  18. lingxingapi/errors.py +143 -0
  19. lingxingapi/fba/__init__.py +0 -0
  20. lingxingapi/fba/api.py +1691 -0
  21. lingxingapi/fba/param.py +250 -0
  22. lingxingapi/fba/route.py +30 -0
  23. lingxingapi/fba/schema.py +987 -0
  24. lingxingapi/fields.py +50 -0
  25. lingxingapi/finance/__init__.py +0 -0
  26. lingxingapi/finance/api.py +3091 -0
  27. lingxingapi/finance/param.py +616 -0
  28. lingxingapi/finance/route.py +44 -0
  29. lingxingapi/finance/schema.py +1243 -0
  30. lingxingapi/product/__init__.py +0 -0
  31. lingxingapi/product/api.py +2643 -0
  32. lingxingapi/product/param.py +934 -0
  33. lingxingapi/product/route.py +49 -0
  34. lingxingapi/product/schema.py +1004 -0
  35. lingxingapi/purchase/__init__.py +0 -0
  36. lingxingapi/purchase/api.py +496 -0
  37. lingxingapi/purchase/param.py +126 -0
  38. lingxingapi/purchase/route.py +11 -0
  39. lingxingapi/purchase/schema.py +215 -0
  40. lingxingapi/sales/__init__.py +0 -0
  41. lingxingapi/sales/api.py +3200 -0
  42. lingxingapi/sales/param.py +723 -0
  43. lingxingapi/sales/route.py +70 -0
  44. lingxingapi/sales/schema.py +1718 -0
  45. lingxingapi/source/__init__.py +0 -0
  46. lingxingapi/source/api.py +1799 -0
  47. lingxingapi/source/param.py +176 -0
  48. lingxingapi/source/route.py +38 -0
  49. lingxingapi/source/schema.py +1011 -0
  50. lingxingapi/tools/__init__.py +0 -0
  51. lingxingapi/tools/api.py +291 -0
  52. lingxingapi/tools/param.py +73 -0
  53. lingxingapi/tools/route.py +8 -0
  54. lingxingapi/tools/schema.py +169 -0
  55. lingxingapi/utils.py +411 -0
  56. lingxingapi/warehourse/__init__.py +0 -0
  57. lingxingapi/warehourse/api.py +1778 -0
  58. lingxingapi/warehourse/param.py +506 -0
  59. lingxingapi/warehourse/route.py +28 -0
  60. lingxingapi/warehourse/schema.py +926 -0
  61. lingxingapi-1.0.0.dist-info/METADATA +67 -0
  62. lingxingapi-1.0.0.dist-info/RECORD +65 -0
  63. lingxingapi-1.0.0.dist-info/WHEEL +5 -0
  64. lingxingapi-1.0.0.dist-info/licenses/LICENSE +22 -0
  65. lingxingapi-1.0.0.dist-info/top_level.txt +1 -0
lingxingapi/utils.py ADDED
@@ -0,0 +1,411 @@
1
+ # -*- coding: utf-8 -*-c
2
+ import datetime
3
+ from typing import Any
4
+ from decimal import Decimal
5
+ from time import time as _unix_time
6
+ from hashlib import md5 as _hashlib_md5
7
+ from base64 import b64encode as _b64encode
8
+ from urllib.parse import quote_plus as _urllib_quote
9
+ from cytimes import Pydt
10
+ from Crypto.Cipher import AES
11
+ from Crypto.Cipher._mode_ecb import EcbMode
12
+ from orjson import dumps as _orjson_dumps, OPT_SORT_KEYS
13
+
14
+
15
+ # Constants ------------------------------------------------------------------------------------------------------------
16
+ BLOCK_SIZE: int = AES.block_size
17
+ DEFAULT_DATE: datetime.date = datetime.date(1970, 1, 1)
18
+
19
+
20
+ # Cryptography ---------------------------------------------------------------------------------------------------------
21
+ def md5_encrypt(text: str) -> str:
22
+ """对字符串进行 MD5 加密
23
+
24
+ :param text `<'str'>`: 要加密的字符串
25
+ :returns `<'str'>`: 返回加密后大写的十六进制字符串
26
+ """
27
+ return _hashlib_md5(text.encode("utf-8")).hexdigest().upper()
28
+
29
+
30
+ def pkc5_pad(text: str) -> str:
31
+ """对字符串进行 PKCS#5 填充
32
+
33
+ :param text `<'str'>`: 要填充的字符串
34
+ :returns `<'bytes'>`: 返回填充后的字节串 (utf-8编码)
35
+ """
36
+ length: int = len(text)
37
+ text += (BLOCK_SIZE - length % BLOCK_SIZE) * chr(BLOCK_SIZE - length % BLOCK_SIZE)
38
+ return text.encode("utf-8")
39
+
40
+
41
+ def generate_sign(params: dict, app_cipher: EcbMode) -> str:
42
+ """根据参数生成请求签名
43
+
44
+ :param params `<'dict'>`: 请求参数字典, 包含公共参数和业务参数
45
+ :param app_cipher `<'EcbMode'>`: 基于 appId 创建 AES-ECB 加密器
46
+ :returns `<'str'>`: 返回生成的签名字符串, 用于请求验证
47
+
48
+ ## 签名步骤
49
+ 1. 按ASCII排序 params, 并过滤 None 或空字符串
50
+ 2. 将参数转换为字符串格式, 如 `key=value` 的形式.
51
+ 若参数值为字典或列表, 则使用 `orjson.dumps` 序列化为 JSON 字符串
52
+ 3. 将所有参数字符串连接成一个以 `&` 分隔的字符串
53
+ 4. 对连接后的字符串使用 `hashlib.md5` 计算 MD5 值, 并转换为大写十六进制字符串
54
+ 5. 使用 AES-ECB 模式加密 MD5 字符串, 使用 PKCS#5 填充
55
+ 6. 对加密结果进行 Base64 编码, 并进行 URL 安全的编码
56
+ """
57
+ # 1–3: 对参数进行排序, 并转换为字符串
58
+ items: list = []
59
+ for key in sorted(params.keys()):
60
+ val = params[key]
61
+ if val is None or val == "":
62
+ continue
63
+ if isinstance(val, (dict, list, tuple)):
64
+ val = _orjson_dumps(val, option=OPT_SORT_KEYS).decode("utf-8")
65
+ items.append("%s=%s" % (key, val))
66
+ canonical: str = "&".join(items)
67
+
68
+ # 4: 使用 hashlib.md5 计算字符串的 MD5 值, 并转换为大写十六进制字符串
69
+ md5hex: str = md5_encrypt(canonical)
70
+
71
+ # 5: AES-ECB加密, 使用 PKCS#5 填充
72
+ encrypt: bytes = app_cipher.encrypt(pkc5_pad(md5hex))
73
+
74
+ # 6: 使用 base64 编码, 并进行 URL 安全的编码
75
+ return _urllib_quote(_b64encode(encrypt).decode("utf-8"), safe="")
76
+
77
+
78
+ # Date & Time ----------------------------------------------------------------------------------------------------------
79
+ def now_ts() -> int:
80
+ """获取当前 Unix 时间戳 (秒)
81
+
82
+ :returns `<'int'>`: 返回当前 Unix 时间戳, 单位为秒
83
+ """
84
+ return int(_unix_time())
85
+
86
+
87
+ # Validator ------------------------------------------------------------------------------------------------------------
88
+ def validate_str(param: str | Any, param_name: str) -> str:
89
+ """验证参数是否为字符串
90
+
91
+ :param param `<'str'>`: 要验证的参数
92
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
93
+ :returns `<'str'>`: 返回验证后的字符串
94
+ """
95
+ if not isinstance(param, str):
96
+ raise ValueError("%s 参数必须是 str 类型, 而非 %s." % (param_name, type(param)))
97
+ return param
98
+
99
+
100
+ def validate_array_of_str(
101
+ array: str | list | tuple | Any,
102
+ param_name: str,
103
+ ) -> list[str]:
104
+ """验证参数是否为字符串列表或元组
105
+
106
+ :param array `<'str/list/tuple'>`: 要验证的参数
107
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
108
+ :returns `<'list[str]'>`: 返回验证后的字符串列表
109
+ """
110
+ if not isinstance(array, (list, tuple)):
111
+ if not isinstance(array, str):
112
+ raise ValueError(
113
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
114
+ )
115
+ array = [array]
116
+ elif not array:
117
+ raise ValueError("%s 参数不能为空." % param_name)
118
+ res: list = []
119
+ for i in array:
120
+ if not isinstance(i, str):
121
+ raise ValueError(
122
+ "%s 参数中的元素必须是 str 类型, 而非 %s." % (param_name, type(i))
123
+ )
124
+ res.append(i)
125
+ return res
126
+
127
+
128
+ def validate_non_empty_str(param: str | Any, param_name: str) -> str:
129
+ """验证参数是否为非空字符串
130
+
131
+ :param param `<'str'>`: 要验证的参数
132
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
133
+ :returns `<'str'>`: 返回验证后的非空字符串
134
+ """
135
+ if not isinstance(param, str):
136
+ raise ValueError("%s 参数必须是 str 类型, 而非 %s." % (param_name, type(param)))
137
+ if not (param := param.strip()):
138
+ raise ValueError("%s 参数必须为非空字符串." % param_name)
139
+ return param
140
+
141
+
142
+ def validate_array_of_non_empty_str(
143
+ array: str | list | tuple | Any,
144
+ param_name: str,
145
+ ) -> list[str]:
146
+ """验证参数是否为非空字符串列表或元组
147
+
148
+ :param array `<'str/list/tuple'>`: 要验证的参数
149
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
150
+ :returns `<'list[str]'>`: 返回验证后的非空字符串列表
151
+ """
152
+ if not isinstance(array, (list, tuple)):
153
+ if not isinstance(array, str):
154
+ raise ValueError(
155
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
156
+ )
157
+ array = [array]
158
+ res: list = []
159
+ for i in array:
160
+ if not isinstance(i, str):
161
+ raise ValueError(
162
+ "%s 参数中的元素必须是 str 类型, 而非 %s." % (param_name, type(i))
163
+ )
164
+ if not (i := i.strip()):
165
+ raise ValueError("%s 参数中的元素必须为非空字符串." % param_name)
166
+ res.append(i)
167
+ return res
168
+
169
+
170
+ def validate_int(param: int | Any, param_name: str) -> int:
171
+ """验证参数是否为整数
172
+
173
+ :param param `<'int'>`: 要验证的参数
174
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
175
+ :returns `<'int'>`: 返回验证后的整数
176
+ """
177
+ if not isinstance(param, int):
178
+ raise ValueError("%s 参数必须是 int 类型, 而非 %s." % (param_name, type(param)))
179
+ return param
180
+
181
+
182
+ def validate_array_of_int(
183
+ array: int | list | tuple | Any,
184
+ param_name: str,
185
+ ) -> list[int]:
186
+ """验证参数是否为整数列表或元组
187
+
188
+ :param array `<'int/list/tuple'>`: 要验证的参数
189
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
190
+ :returns `<'list[int]'>`: 返回验证后的整数列表
191
+ """
192
+ if not isinstance(array, (list, tuple)):
193
+ if not isinstance(array, int):
194
+ raise ValueError(
195
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
196
+ )
197
+ array = [array]
198
+ elif not array:
199
+ raise ValueError("%s 参数不能为空." % param_name)
200
+ res: list = []
201
+ for i in array:
202
+ if not isinstance(i, int):
203
+ raise ValueError(
204
+ "%s 参数中的元素必须是 int 类型, 而非 %s." % (param_name, type(i))
205
+ )
206
+ res.append(i)
207
+ return res
208
+
209
+
210
+ def validate_unsigned_int(param: int | Any, param_name: str) -> int:
211
+ """验证参数是否为正整数
212
+
213
+ :param param `<'int'>`: 要验证的参数
214
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
215
+ :returns `<'int'>`: 返回验证后的正整数
216
+ """
217
+ if not isinstance(param, int):
218
+ raise ValueError("%s 参数必须是 int 类型, 而非 %s." % (param_name, type(param)))
219
+ if param < 0:
220
+ raise ValueError("%s 参数必须为正整数, 而非 %s." % (param_name, param))
221
+ return param
222
+
223
+
224
+ def validate_array_of_unsigned_int(
225
+ array: int | list | tuple | Any,
226
+ param_name: str,
227
+ ) -> list[int]:
228
+ """验证参数是否为正整数列表或元组
229
+
230
+ :param array `<'int/list/tuple'>`: 要验证的参数
231
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
232
+ :returns `<'list[int]'>`: 返回验证后的正整数列表
233
+ """
234
+ if not isinstance(array, (list, tuple)):
235
+ if not isinstance(array, int):
236
+ raise ValueError(
237
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
238
+ )
239
+ array = [array]
240
+ elif not array:
241
+ raise ValueError("%s 参数不能为空." % param_name)
242
+ res: list = []
243
+ for i in array:
244
+ if not isinstance(i, int):
245
+ raise ValueError(
246
+ "%s 参数中的元素必须是 int 类型, 而非 %s." % (param_name, type(i))
247
+ )
248
+ if i < 0:
249
+ raise ValueError("%s 参数中的元素必须为正整数." % param_name)
250
+ res.append(i)
251
+ return res
252
+
253
+
254
+ def validate_float(param: float | Any, param_name: str) -> float:
255
+ """验证参数是否为浮点数
256
+
257
+ :param param `<'float'>`: 要验证的参数
258
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
259
+ :returns `<'float'>`: 返回验证后的浮点数
260
+ """
261
+ if not isinstance(param, float):
262
+ try:
263
+ param = float(param)
264
+ except Exception:
265
+ raise ValueError(
266
+ "%s 参数必须是 float 类型, 而非 %s." % (param_name, type(param))
267
+ )
268
+ return param
269
+
270
+
271
+ def validate_array_of_float(
272
+ array: float | list | tuple | Any,
273
+ param_name: str,
274
+ ) -> list[float]:
275
+ """验证参数是否为浮点数列表或元组
276
+
277
+ :param array `<'float/list/tuple'>`: 要验证的参数
278
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
279
+ :returns `<'list[float]'>`: 返回验证后的浮点数列表
280
+ """
281
+ if not isinstance(array, (list, tuple)):
282
+ if not isinstance(array, float):
283
+ raise ValueError(
284
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
285
+ )
286
+ array = [array]
287
+ elif not array:
288
+ raise ValueError("%s 参数不能为空." % param_name)
289
+ res: list = []
290
+ for i in array:
291
+ if not isinstance(i, float):
292
+ try:
293
+ i = float(i)
294
+ except Exception:
295
+ raise ValueError(
296
+ "%s 参数中的元素必须是 float 类型, 而非 %s." % (param_name, type(i))
297
+ )
298
+ res.append(i)
299
+ return res
300
+
301
+
302
+ def validate_unsigned_float(param: float | Any, param_name: str) -> float:
303
+ """验证参数是否为正浮点数
304
+
305
+ :param param `<'float'>`: 要验证的参数
306
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
307
+ :returns `<'float'>`: 返回验证后的正浮点数
308
+ """
309
+ if not isinstance(param, float):
310
+ try:
311
+ param = float(param)
312
+ except Exception:
313
+ raise ValueError(
314
+ "%s 参数必须是 float 类型, 而非 %s." % (param_name, type(param))
315
+ )
316
+ if param <= 0:
317
+ raise ValueError("%s 参数必须为正浮点数, 而非 %s." % (param_name, param))
318
+ return param
319
+
320
+
321
+ def validate_array_of_unsigned_float(
322
+ array: float | list | tuple | Any,
323
+ param_name: str,
324
+ ) -> list[float]:
325
+ """验证参数是否为正浮点数列表或元组
326
+
327
+ :param array `<'float/list/tuple'>`: 要验证的参数
328
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
329
+ :returns `<'list[float]'>`: 返回验证后的正浮点数列表
330
+ """
331
+ if not isinstance(array, (list, tuple)):
332
+ if not isinstance(array, float):
333
+ raise ValueError(
334
+ "%s 参数必须是列表或元组类型, 而非 %s." % (param_name, type(array))
335
+ )
336
+ array = [array]
337
+ elif not array:
338
+ raise ValueError("%s 参数不能为空." % param_name)
339
+ res: list = []
340
+ for i in array:
341
+ if not isinstance(i, float):
342
+ try:
343
+ i = float(i)
344
+ except Exception:
345
+ raise ValueError(
346
+ "%s 参数中的元素必须是 float 类型, 而非 %s." % (param_name, type(i))
347
+ )
348
+ if i <= 0:
349
+ raise ValueError("%s 参数中的元素必须为正浮点数." % param_name)
350
+ res.append(i)
351
+ return res
352
+
353
+
354
+ def validate_datetime(
355
+ dt: str | datetime.date | datetime.datetime | None,
356
+ support_none: bool,
357
+ param_name: str,
358
+ ) -> Pydt:
359
+ """检验并解析日期字符串或日期对象
360
+
361
+ :param dt `<'str/date/datetime'>`: 日期字符串或日期对象
362
+
363
+ - 如果是字符串, 必须是有效的日期字符串, 如: `"2025-07"`, `"2025-07-01"`
364
+ - 如果是 `date` 或 `datetime` 对象, 将自动转换为 `YYYY-MM` 格式
365
+ - 如果为 `None`, 则使用当前月份作为参数
366
+
367
+ :param support_none `<'bool'>`: 是否支持参数 `dt` 为 `None`
368
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
369
+ :returns `<'Pydt'>`: 返回一个 `Pydt` 的 datetime 对象
370
+ """
371
+ if not support_none and dt is None:
372
+ raise ValueError("%s 参数不能为 None." % param_name)
373
+ try:
374
+ return Pydt.parse(dt, default=DEFAULT_DATE)
375
+ except Exception as err:
376
+ raise ValueError(
377
+ "%s 参数值 %r 无法被解析为日期对象." % (param_name, dt)
378
+ ) from err
379
+
380
+
381
+ def validate_exchange_rate(rate: str | float | Decimal, param_name: str) -> Decimal:
382
+ """检验并解析汇率字符串或数字
383
+
384
+ :param rate `<'str/float/Decimal'>`: 汇率字符串或数字
385
+ :param param_name `<'str'>`: 参数名称, 用于构建错误信息
386
+ :returns `<'Decimal'>`: 返回一个精确的 Decimal 对象
387
+ """
388
+ if isinstance(rate, float):
389
+ try:
390
+ rate = Decimal(str(rate))
391
+ except Exception as err:
392
+ raise ValueError(
393
+ "%s 参数值 %r 无法被解析为 Decimal 对象." % (param_name, rate)
394
+ ) from err
395
+ elif isinstance(rate, str):
396
+ try:
397
+ rate = Decimal(rate)
398
+ except Exception as err:
399
+ raise ValueError(
400
+ "%s 参数值 %r 无法被解析为 Decimal 对象." % (param_name, rate)
401
+ ) from err
402
+ elif not isinstance(rate, Decimal):
403
+ try:
404
+ rate = Decimal(str(rate))
405
+ except Exception as err:
406
+ raise ValueError(
407
+ "%s 参数值 %r 无法被解析为 Decimal 对象." % (param_name, rate)
408
+ ) from err
409
+ if not rate.is_finite() or rate <= 0:
410
+ raise ValueError("%s 参数值必须为正整数, 而非 %s." % (param_name, rate))
411
+ return rate.quantize(Decimal("0.0000000000")) # 保留10位小数
File without changes