ey-commerce-lib 1.0.6__py3-none-any.whl → 1.0.7__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 ey-commerce-lib might be problematic. Click here for more details.

File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class ResponseCodeEnum(IntEnum):
5
+ # 登录验证失败
6
+ LOGIN_VALIDATION_FAILED = 4001
7
+ # 操作成功
8
+ SUCCESS = 0
@@ -0,0 +1,280 @@
1
+ import asyncio
2
+ import base64
3
+ import logging
4
+ from typing import Callable
5
+
6
+ from httpx import AsyncClient, ReadTimeout
7
+ from playwright.async_api import async_playwright, TimeoutError
8
+ import ddddocr
9
+
10
+ from ey_commerce_lib.four_seller.constant.response import ResponseCodeEnum
11
+ from ey_commerce_lib.four_seller.parser.order import parse_order
12
+ from ey_commerce_lib.four_seller.schemas.query.order import FourSellerOrderQueryModel
13
+ from ey_commerce_lib.four_seller.schemas.vo.order import FourSellerOrderVO
14
+ from ey_commerce_lib.model import Page
15
+ from ey_commerce_lib.utils.close import safe_close
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class FourSellerClient:
21
+
22
+ def __init__(self, user_name: str,
23
+ password: str,
24
+ user_token: str,
25
+ login_success_call_back: Callable,
26
+ sem: int = 10):
27
+ self.user_name = user_name
28
+ self.password = password
29
+ self.user_token = user_token
30
+ # 登录成功回调函数
31
+ self.login_success_call_back = login_success_call_back
32
+ # 信号量
33
+ self.__sem = asyncio.Semaphore(sem)
34
+ # 自动登录锁
35
+ self.__login_lock = asyncio.Lock()
36
+ # 当前登录任务的 future
37
+ self.__login_in_progress = None
38
+ # ddddocr实例
39
+ self.__ocr = ddddocr.DdddOcr()
40
+ # 异步客户端
41
+ self.__async_client = AsyncClient(base_url="https://www.4seller.com", cookies={"userToken": user_token},
42
+ headers={
43
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
44
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 "
45
+ "Safari/537.36"
46
+ })
47
+
48
+ async def __auto_login_4seller(self) -> str:
49
+ """
50
+ 自动登录4seller
51
+ :return: 登录
52
+ """
53
+ browser = None
54
+ context = None
55
+ page = None
56
+ try:
57
+ async with async_playwright() as p:
58
+ browser = await p.chromium.launch(
59
+ headless=True,
60
+ args=[
61
+ '--start-maximized',
62
+ '--disable-blink-features=AutomationControlled',
63
+ ]
64
+ )
65
+
66
+ context = await browser.new_context(
67
+ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
68
+ "(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
69
+ viewport={"width": 1920, "height": 1080},
70
+ locale='zh-CN', # 中文环境
71
+ timezone_id='Asia/Shanghai', # 中国时区
72
+ geolocation={"longitude": 116.4074, "latitude": 39.9042}, # 北京位置
73
+ permissions=["geolocation"]
74
+ )
75
+ page = await context.new_page()
76
+ await page.goto('https://www.4seller.com/login.html')
77
+ # 等待元素出现
78
+ await page.locator('.el-input__inner').first.wait_for()
79
+ input_locators = await page.locator('.el-input__inner').all()
80
+ username_input_element = input_locators[0]
81
+ password_input_element = input_locators[1]
82
+ await username_input_element.fill(self.user_name)
83
+ await password_input_element.fill(self.password)
84
+ login_button_element = page.locator('.el-button.sign_up')
85
+ await login_button_element.click()
86
+ # 获取验证码图片
87
+ capcha_code_img_element = page.locator('.el-input__wrapper img')
88
+ await capcha_code_img_element.wait_for()
89
+ captcha_code_input_element = page.locator('[autocomplete="new-password"]')
90
+ login_count = 1
91
+ # 只能允许一百次登录
92
+ while login_count <= 100:
93
+ # 等图片 src 被赋值
94
+ max_attempts = 10
95
+ for attempt in range(max_attempts):
96
+ src = await capcha_code_img_element.get_attribute("src")
97
+ if src and src.strip() != "":
98
+ break
99
+ await page.wait_for_timeout(500)
100
+ else:
101
+ raise Exception("获取不到图片验证码")
102
+ # 识别验证码
103
+ # 如果有前缀 "data:image/xxx;base64,", 需要去掉
104
+ if ',' in src:
105
+ base64_str = src.split(',')[1]
106
+
107
+ # 解码 base64 为字节流
108
+ img_bytes = base64.b64decode(base64_str)
109
+ captcha_code = self.__ocr.classification(img_bytes)
110
+ # 回填验证码
111
+ await captcha_code_input_element.fill(captcha_code)
112
+ # 再次点击登录按钮
113
+ await login_button_element.click()
114
+ try:
115
+ await page.wait_for_selector(".el-message.el-message--error", timeout=10000)
116
+ except TimeoutError:
117
+ # 获取userToken的cookie
118
+ cookies = await context.cookies()
119
+ for cookie in cookies:
120
+ if cookie["name"] == "userToken":
121
+ userToken = cookie["value"]
122
+ # 返回token
123
+ return userToken
124
+ # 防止验证码切换过快
125
+ await page.wait_for_timeout(2000)
126
+ login_count += 1
127
+ except Exception as e:
128
+ raise e
129
+ finally:
130
+ # 不管成功与否函数都要关闭
131
+ for close_obj in (page, context, browser):
132
+ await safe_close(close_obj)
133
+
134
+ async def __refresh_user_token(self):
135
+ """
136
+ 刷新token
137
+ :return:
138
+ """
139
+ # 如果有登录任务正在运行
140
+ if self.__login_in_progress is not None:
141
+ # 已有协程在处理登录,我们等待它完成
142
+ await self.__login_in_progress
143
+ return
144
+
145
+ # 没有登录任务,当前协程负责登录
146
+ self.__login_in_progress = asyncio.get_event_loop().create_future()
147
+ # 检测锁,确保只有一个线程在登录
148
+ async with self.__login_lock:
149
+ try:
150
+ user_token = await self.__auto_login_4seller()
151
+ except Exception as e:
152
+ # 如果future没有被终结掉手动设置终结future
153
+ if not self.__login_in_progress.done():
154
+ self.__login_in_progress.set_exception(e)
155
+ logger.error(f'FourSeller自动登录失败 {e}')
156
+ raise Exception(f'FourSeller登录身份失效, 尝试自动登录失败 {e}')
157
+ else:
158
+ # 登录成功后重置token
159
+ self.user_token = user_token
160
+ # 清空原cookies
161
+ self.__async_client.cookies.clear()
162
+ # 重新设置客户端的cookie
163
+ self.__async_client.cookies.set('userToken', self.user_token, domain='www.4seller.com', path='/')
164
+ # 如果future没有被终结掉手动设置终结future,否则会抛出异常影响回调执行
165
+ if not self.__login_in_progress.done():
166
+ self.__login_in_progress.set_result(True)
167
+ # 回调函数可能会失败,但是不能影响其它协程操作
168
+ try:
169
+ await self.login_success_call_back(user_token)
170
+ except Exception as e:
171
+ logger.error(f'FourSeller登录成功回调函数执行失败 {e}')
172
+ pass
173
+ finally:
174
+ # 提示登录任务完成
175
+ self.__login_in_progress = None
176
+
177
+ async def __request(self, url, method, retry_count=0, **kwargs):
178
+ """
179
+ 4seller基本请求
180
+ :param url: url路径
181
+ :param method: 请求方法
182
+ :param kwargs: 请求参数
183
+ :return:
184
+ """
185
+ try:
186
+ async with self.__sem:
187
+ response = await self.__async_client.request(method, url, **kwargs)
188
+ except ReadTimeout:
189
+ raise Exception(f'FourSeller请求接口超时')
190
+ except Exception as e:
191
+ logger.error(f'FourSeller请求接口失败{e}')
192
+ raise Exception(f'FourSeller请求接口失败{e}')
193
+ else:
194
+ try:
195
+ data = response.json()
196
+ except Exception as e:
197
+ logger.error(f'FourSeller请求,json序列化失败{e},原始数据{response.text}')
198
+ raise Exception(f'FourSeller请求,json序列化失败{e},原始数据{response.text}')
199
+ else:
200
+ if data.get('code') == ResponseCodeEnum.LOGIN_VALIDATION_FAILED:
201
+ # 刷新token
202
+ await self.__refresh_user_token()
203
+ if retry_count < 1:
204
+ # 最多重试一次
205
+ return await self.__request(url, method, 1, **kwargs)
206
+ else:
207
+ raise Exception(f'FourSeller登录成功后,再次请求登录验证失败, 请求接口{url},返回信息{data}')
208
+ return data
209
+
210
+ async def __get(self, url, **kwargs):
211
+ return await self.__request(url, 'GET', **kwargs)
212
+
213
+ async def __post(self, url, **kwargs):
214
+ return await self.__request(url, 'POST', **kwargs)
215
+
216
+ async def __put(self, url, **kwargs):
217
+ return await self.__request(url, 'PUT', **kwargs)
218
+
219
+ async def __delete(self, url, **kwargs):
220
+ return await self.__request(url, 'DELETE', **kwargs)
221
+
222
+ async def __order_page_api(self, query_params: FourSellerOrderQueryModel):
223
+ return await self.__post('/api/v2/order/page', json=query_params.model_dump(by_alias=True))
224
+
225
+ async def __order_page_history_api(self, query_params: FourSellerOrderQueryModel):
226
+ return await self.__post('/api/v2/order/history/page', json=query_params.model_dump(by_alias=True))
227
+
228
+ async def order_page(self, query_params: FourSellerOrderQueryModel) -> Page[FourSellerOrderVO]:
229
+ page_data = await self.__order_page_api(query_params)
230
+ # 能到这里说明登录验证什么的都没有问题,接口返回代码也是代表成功
231
+ if page_data.get('code') != ResponseCodeEnum.SUCCESS:
232
+ raise Exception(f'FourSeller请求接口失败, 请求接口{page_data}')
233
+ return parse_order(page_data.get('data'))
234
+
235
+ async def order_page_history(self, query_params: FourSellerOrderQueryModel) -> Page[FourSellerOrderVO]:
236
+ page_data = await self.__order_page_history_api(query_params)
237
+ # 能到这里说明登录验证什么的都没有问题,接口返回代码也是代表成功
238
+ if page_data.get('code') != ResponseCodeEnum.SUCCESS:
239
+ raise Exception(f'FourSeller拉取历史分页订单失败, 请求接口{page_data}')
240
+ return parse_order(page_data.get('data'))
241
+
242
+ async def list_order(self, query_params: FourSellerOrderQueryModel) -> list[FourSellerOrderVO]:
243
+ # 获取分页信息
244
+ order_list = list()
245
+ page = await self.order_page(query_params)
246
+ # 采用并发提高速度
247
+ page_order_task_list = list()
248
+ for page_number in range(1, page.total_page + 1):
249
+ page_order_task_list.append(
250
+ self.order_page(query_params.copy(update={"page_current": page_number}))
251
+ )
252
+ page_list = await asyncio.gather(*page_order_task_list)
253
+ # 所有的数据
254
+ for every_page in page_list:
255
+ # 插入数据
256
+ order_list.extend(every_page.records)
257
+ return order_list
258
+
259
+ async def list_history_order(self, query_params: FourSellerOrderQueryModel):
260
+ # 获取分页信息
261
+ order_history_list = list()
262
+ page = await self.order_page_history(query_params)
263
+ # 采用并发提高速度
264
+ page_order_task_list = list()
265
+ for page_number in range(1, page.total_page + 1):
266
+ page_order_task_list.append(self.order_page_history(
267
+ query_params.copy(update={"page_current": page_number})
268
+ ))
269
+ page_list = await asyncio.gather(*page_order_task_list)
270
+ # 所有的数据
271
+ for every_page in page_list:
272
+ # 插入数据
273
+ order_history_list.extend(every_page.records)
274
+ return order_history_list
275
+
276
+ async def __aenter__(self):
277
+ return self
278
+
279
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
280
+ await self.__async_client.aclose()
File without changes
@@ -0,0 +1,19 @@
1
+ import math
2
+
3
+ from ey_commerce_lib.four_seller.schemas.vo.order import FourSellerOrderVO
4
+ from ey_commerce_lib.model import Page
5
+
6
+
7
+ def parse_order(data: dict) -> Page[FourSellerOrderVO]:
8
+ # 计算total_page
9
+ page_size = data.get('pageSize')
10
+ total = data.get('total')
11
+ page_number = data.get('pageCurrent')
12
+ total_page = math.ceil(total / page_size) if total > 0 else 0
13
+ return Page(
14
+ records=[FourSellerOrderVO(**record) for record in data.get('records')],
15
+ total=total,
16
+ page_size=page_size,
17
+ page_number=page_number,
18
+ total_page=total_page
19
+ )
File without changes
File without changes
@@ -0,0 +1,101 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+ from ey_commerce_lib.utils.str import to_camel
4
+
5
+
6
+ class FourSellerOrderQueryModel(BaseModel):
7
+ # 页码
8
+ page_current: int = 1
9
+ # 每页条数
10
+ page_size: int = 100
11
+ # 平台
12
+ platform: str = ""
13
+ # 店铺id列表
14
+ shop_id_list: list[str] = []
15
+ # 仓库id 页面未知来源
16
+ warehouse_id: str = ""
17
+ # 物流id 例如: 13554
18
+ logistics_service_id: str = ""
19
+ # 物流服务id映射关系父子id,id连接起来 例如: ["470", "13554"]
20
+ logistics_service_map: list[str] = []
21
+ # 单品单数: onlyOne 全部包裹类型: "" 单品多数: gtOne 多品: multiple
22
+ item_type: str = ""
23
+ """
24
+ 打印状态 全部: "" 面单已打印: labelPrinted 面单未打印: labelUnPrinted 装箱已打印: packingPrinted 装箱未打印: packingUnPrinted
25
+ 拣货单已打印: pickPrinted 拣货单未打印: pickUnPrinted
26
+ """
27
+ print_status: str = ""
28
+ # 买家指定
29
+ shipping_method: list[str] = []
30
+ # 搜索类型 订单号: orderNo 物流跟踪号: trackNo 其余请参考页面
31
+ search_type: str = "orderNo"
32
+ # 搜索方式 模糊: like 精确: exact
33
+ search_method: str = "exact"
34
+ # 搜索内容
35
+ search_value: str = ""
36
+ # 商品总数最小
37
+ item_min: str = ""
38
+ # 商品总数最大
39
+ item_max: str = ""
40
+ # 重量最小
41
+ weight_min: str = ""
42
+ # 重量最大
43
+ weight_max: str = ""
44
+ # 重量单位 lb oz kg g
45
+ weight_unit: str = "lb"
46
+ # 到期时间 如果是数字就是小时 如果为空字符串是全部
47
+ expires_hour: str | int = ""
48
+ hours: str = ""
49
+ # 平台状态 待发货: Awaiting_Shipment 待揽收: Awaiting_Collection 其它参考页面
50
+ platform_status: str = ""
51
+ # 发货地址id 如果为空字符串就是没有选择
52
+ ship_from_address_id: str | int = ""
53
+ # 国家编码 具体参考筛选页面
54
+ country_code: str = ""
55
+ # 国家名称 具体参考筛选页面
56
+ country_name: str = ""
57
+ # 省份或者洲
58
+ state: str = ""
59
+ # 买家备注 空字符串未选 有:1 无: 0
60
+ buyer_note: str | int = ""
61
+ # 卖家备注 空字符串未选 有:1 无: 0
62
+ seller_note: str = ""
63
+ # 系统备注: 空字符串未选 有:1 无: 0
64
+ internal_note: str = ""
65
+ # 平台标记 PRE_SALE: Temu预售
66
+ platform_marker: str = ""
67
+ # 下单时间 这个时间比上海时间要晚7个小时
68
+ date_query_create_start_time: str = ""
69
+ date_query_create_end_time: str = ""
70
+ # 付款时间
71
+ date_query_paid_start_time: str = ""
72
+ date_query_paid_end_time: str = ""
73
+ # 发货时间
74
+ date_query_ship_start_time: str = ""
75
+ date_query_ship_end_time: str = ""
76
+ # 打印面单时间
77
+ date_query_print_label_start_time: str = ""
78
+ date_query_print_label_end_time: str = ""
79
+ # 取消订单时间
80
+ date_query_cancel_start_time: str = ""
81
+ date_query_cancel_end_time: str = ""
82
+ # 标签页 具体参考筛选页面
83
+ tag_id_list: list[str] = []
84
+ # 订单状态 全部: 空字符串 未付款: un_paid 已发货: shipped 待处理: to_ship 风控中: on_hold 处理中: in_process 已签收: delivered 参考页面
85
+ all_order_status: str = ""
86
+ # 订单状态: 全部: all 已发货: shipped
87
+ order_status: str = "all"
88
+ # 排序字段
89
+ order_by: str = "orderPaidTime"
90
+ # 排序方式 asc: 升序 desc: 降序
91
+ desc: str = "asc"
92
+
93
+ country: str = ""
94
+ note_query: str = ""
95
+ tag_query: str = ""
96
+ exception_type: str = ""
97
+
98
+ model_config = ConfigDict(
99
+ alias_generator=to_camel,
100
+ populate_by_name=True
101
+ )
File without changes
@@ -0,0 +1,437 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field, ConfigDict
3
+
4
+
5
+ class FourSellerOrderAddressVo(BaseModel):
6
+ # 地址 ID
7
+ id: int
8
+
9
+ # 收件人姓名
10
+ name: str
11
+
12
+ # 城市
13
+ city: str
14
+
15
+ # 邮编
16
+ postal_code: Optional[str] = Field(..., alias="postalCode")
17
+
18
+ # 完整地址
19
+ address: str
20
+
21
+ # 电话
22
+ phone: str
23
+
24
+ # 邮箱
25
+ email: str
26
+
27
+ # 国家代码
28
+ country_code: str = Field(..., alias="countryCode")
29
+
30
+ # 区/县
31
+ county: Optional[str]
32
+
33
+ # 地址行1
34
+ address1: str
35
+
36
+ # 地址行2
37
+ address2: Optional[str]
38
+
39
+ # 地址行3
40
+ address3: Optional[str] = Field(None)
41
+
42
+ # 州/省
43
+ state: Optional[str] = Field(None)
44
+
45
+ # 地址备注
46
+ msg: Optional[str] = Field(None)
47
+
48
+ # 地址备注映射
49
+ msg_map: Optional[dict] = Field(None, alias="msgMap")
50
+
51
+ # 是否住宅
52
+ residential: Optional[bool]
53
+
54
+ # 是否检查地址
55
+ check_address: bool = Field(..., alias="checkAddress")
56
+
57
+ model_config = ConfigDict(
58
+ populate_by_name=True
59
+ )
60
+
61
+
62
+ class FourSellerTimeInfoVO(BaseModel):
63
+ # 订单支付时间
64
+ order_paid_time: Optional[str] = Field(..., alias="orderPaidTime")
65
+
66
+ # 订单创建时间
67
+ order_create_time: str = Field(..., alias="orderCreateTime")
68
+
69
+ # 最晚发货日期
70
+ latest_ship_date: Optional[str] = Field(..., alias="latestShipDate")
71
+
72
+ # 发货时间
73
+ order_shipped_time: Optional[str] = Field(..., alias="orderShippedTime")
74
+
75
+ # 最晚送达日期
76
+ latest_delivery_date: Optional[str] = Field(..., alias="latestDeliveryDate")
77
+
78
+ # 取消时间
79
+ cancel_time: Optional[str] = Field(..., alias="cancelTime")
80
+
81
+ # 剩余天数
82
+ leave_day: int = Field(..., alias="leaveDay")
83
+
84
+ model_config = ConfigDict(
85
+ populate_by_name=True
86
+ )
87
+
88
+
89
+ class FourSellerPackageInfoVO(BaseModel):
90
+ # 重量
91
+ weight: Optional[float]
92
+
93
+ # 重量单位
94
+ weight_unit: Optional[str] = Field(None, alias="weightUnit")
95
+
96
+ # 包裹 ID
97
+ package_id: Optional[int] = Field(None, alias="packageId")
98
+
99
+ # 包裹类型
100
+ package_type: Optional[str] = Field(None, alias="packageType")
101
+
102
+ # 包裹名称
103
+ package_name: Optional[str] = Field(None, alias="packageName")
104
+
105
+ # 平台包裹类型
106
+ platform_package_type: Optional[str] = Field(None, alias="platformPackageType")
107
+
108
+ # 长度
109
+ length: Optional[float]
110
+
111
+ # 宽度
112
+ width: Optional[float]
113
+
114
+ # 高度
115
+ height: Optional[float]
116
+
117
+ # 尺寸单位
118
+ dimension_unit: Optional[str] = Field(None, alias="dimensionUnit")
119
+
120
+ model_config = ConfigDict(
121
+ populate_by_name=True
122
+ )
123
+
124
+
125
+ class FourSellerShippingServiceInfoVO(BaseModel):
126
+ # 保险金额
127
+ insurance_amount: Optional[float] = Field(None, alias="insuranceAmount")
128
+
129
+ # 保险币种
130
+ insurance_currency: Optional[str] = Field(None, alias="insuranceCurrency")
131
+
132
+ # 物流服务
133
+ shipping_service: Optional[str] = Field(None, alias="shippingService")
134
+
135
+ # 承运商
136
+ carrier: Optional[str]
137
+
138
+ # 运费
139
+ shipping_fee: Optional[float] = Field(None, alias="shippingFee")
140
+
141
+ # 币种
142
+ currency: Optional[str]
143
+
144
+ # 预估送达天数
145
+ estimated_delivery_days: Optional[int] = Field(None, alias="estimatedDeliveryDays")
146
+
147
+ # 物流授权 ID
148
+ logistics_auth_id: Optional[int] = Field(None, alias="logisticsAuthId")
149
+
150
+ # 物流承运商 ID
151
+ logistics_carrier_id: Optional[int] = Field(None, alias="logisticsCarrierId")
152
+
153
+ # 物流承运商代码
154
+ logistics_carrier_code: Optional[str] = Field(None, alias="logisticsCarrierCode")
155
+
156
+ # 物流承运商名称
157
+ logistics_carrier_name: Optional[str] = Field(None, alias="logisticsCarrierName")
158
+
159
+ # 物流平台
160
+ logistics_platform: Optional[str] = Field(None, alias="logisticsPlatform")
161
+
162
+ # 物流服务 ID
163
+ logistics_service_id: Optional[int] = Field(None, alias="logisticsServiceId")
164
+
165
+ # 物流服务代码
166
+ logistics_service_code: Optional[str] = Field(None, alias="logisticsServiceCode")
167
+
168
+ # 物流服务类型
169
+ logistics_service_type: Optional[str] = Field(None, alias="logisticsServiceType")
170
+
171
+ # 物流服务名称
172
+ logistics_service_name: Optional[str] = Field(None, alias="logisticsServiceName")
173
+
174
+ # 平台物流服务 ID
175
+ platform_shipping_service_id: Optional[int] = Field(None, alias="platformShippingServiceId")
176
+
177
+ # 空白箱
178
+ blank_box: Optional[int] = Field(None, alias="blankBox")
179
+
180
+ # 阻止 AMZL
181
+ block_amzl: Optional[int] = Field(None, alias="blockAmzl")
182
+
183
+ # ERP 预估发货日期
184
+ erp_estimated_shipping_date: Optional[str] = Field(None, alias="erpEstimatedShippingDate")
185
+
186
+ # 配送确认类型
187
+ delivery_confirmation_type: Optional[str] = Field(None, alias="deliveryConfirmationType")
188
+
189
+ # 标签价格
190
+ label_price: Optional[float] = Field(None, alias="labelPrice")
191
+
192
+ # 预估运费
193
+ estimated_shipping_price: Optional[float] = Field(None, alias="estimatedShippingPrice")
194
+
195
+ model_config = ConfigDict(
196
+ populate_by_name=True
197
+ )
198
+
199
+
200
+ class FourSellerOrderItemInfoVO(BaseModel):
201
+ # 订单商品 ID
202
+ order_item_id: int = Field(..., alias="orderItemId")
203
+
204
+ # 订单 ID
205
+ order_id: int = Field(..., alias="orderId")
206
+
207
+ # 卖家 SKU
208
+ seller_sku: Optional[str] = Field(..., alias="sellerSku")
209
+
210
+ # 商品标题
211
+ title: str
212
+
213
+ # 图片 URL
214
+ img_url: Optional[str] = Field(None, alias="imgUrl")
215
+
216
+ # 列表 URL
217
+ listing_url: Optional[str] = Field(None, alias="listingUrl")
218
+
219
+ # SKU ID
220
+ sku_id: Optional[int] = Field(..., alias="skuId")
221
+
222
+ # SKU
223
+ sku: Optional[str] = Field(None)
224
+
225
+ # 数量
226
+ quantity: int
227
+
228
+ # 变体属性
229
+ variant_attr: Optional[str] = Field(None, alias="variantAttr")
230
+
231
+ # 单价
232
+ item_price: Optional[float] = Field(..., alias="itemPrice")
233
+
234
+ # 币种
235
+ currency: Optional[str] = Field(None)
236
+
237
+ # 是否占用库存
238
+ occupy: Optional[bool] = Field(None)
239
+
240
+ # 商品是否未找到
241
+ product_not_found: bool = Field(..., alias="productNotFound")
242
+
243
+ # 地址是否有效
244
+ address_valid: bool = Field(..., alias="addressValid")
245
+
246
+ # 地址错误信息
247
+ address_error_msg: Optional[str] = Field(None, alias="addressErrorMsg")
248
+
249
+ # 来源 URL
250
+ source_url: Optional[str] = Field(None, alias="sourceUrl")
251
+
252
+ # 是否预售
253
+ pre_sale: int = Field(..., alias="preSale")
254
+
255
+ model_config = ConfigDict(
256
+ populate_by_name=True
257
+ )
258
+
259
+
260
+ class FourSellerOrderVO(BaseModel):
261
+ # 订单 ID
262
+ order_id: int = Field(..., alias="orderId")
263
+
264
+ # 金额
265
+ amount: Optional[float] = Field(None)
266
+
267
+ # 币种
268
+ currency: Optional[str] = Field(None)
269
+
270
+ # 仓库 ID
271
+ warehouse_id: int = Field(..., alias="warehouseId")
272
+
273
+ # 仓库类型
274
+ warehouse_type: str = Field(..., alias="warehouseType")
275
+
276
+ # 仓库代码
277
+ warehouse_code: Optional[str] = Field(None, alias="warehouseCode")
278
+
279
+ # 平台仓名称
280
+ platform_warehouse_name: Optional[str] = Field(None, alias="platformWarehouseName")
281
+
282
+ # 平台物流方式
283
+ platform_shipment_method: Optional[str] = Field(None, alias="platformShipmentMethod")
284
+
285
+ # 支付状态
286
+ paid_status: Optional[str] = Field(None, alias="paidStatus")
287
+
288
+ # 订单状态
289
+ order_status: str = Field(..., alias="orderStatus")
290
+
291
+ # 平台订单状态
292
+ platform_order_status: str = Field(..., alias="platformOrderStatus")
293
+
294
+ # 取消原因
295
+ cancel_reason: Optional[str] = Field(None, alias="cancelReason")
296
+
297
+ # 平台
298
+ platform: str
299
+
300
+ # 平台订单号
301
+ platform_order_id: str = Field(..., alias="platformOrderId")
302
+
303
+ # 平台订单编号
304
+ platform_order_no: str = Field(..., alias="platformOrderNo")
305
+
306
+ # 店铺 ID
307
+ shop_id: int = Field(..., alias="shopId")
308
+
309
+ # 店铺名称
310
+ shop_name: Optional[str] = Field(None, alias="shopName")
311
+
312
+ # 包裹名称
313
+ package_name: Optional[str] = Field(None, alias="packageName")
314
+
315
+ # 买家备注
316
+ buyer_memo: Optional[str] = Field(None, alias="buyerMemo")
317
+
318
+ # 卖家备注
319
+ seller_memo: Optional[str] = Field(None, alias="sellerMemo")
320
+
321
+ # 内部备注
322
+ internal_memo: Optional[str] = Field(None, alias="internalMemo")
323
+
324
+ # 发货单号
325
+ shipment_no: Optional[str] = Field(None, alias="shipmentNo")
326
+
327
+ # 是否三方仓
328
+ third_party: bool = Field(..., alias="thirdParty")
329
+
330
+ # 是否打印标签
331
+ print_label: bool = Field(..., alias="printLabel")
332
+
333
+ # 是否打印装箱单
334
+ print_packing_slip: bool = Field(..., alias="printPackingSlip")
335
+
336
+ # 是否打印拣货单
337
+ print_pick: bool = Field(..., alias="printPick")
338
+
339
+ # 配送选项
340
+ delivery_option_type: Optional[int] = Field(None, alias="deliveryOptionType")
341
+
342
+ # 买标状态
343
+ buy_label_process_status: str = Field(..., alias="buyLabelProcessStatus")
344
+
345
+ # 错误信息
346
+ error_message: Optional[str] = Field(None, alias="errorMessage")
347
+
348
+ # 发货类型
349
+ shipment_type: Optional[str] = Field(..., alias="shipmentType")
350
+
351
+ # 用户设置发货类型
352
+ user_setting_shipment_type: str = Field(..., alias="userSettingShipmentType")
353
+
354
+ # 发货地址 ID
355
+ ship_from_address_id: Optional[int] = Field(None, alias="shipFromAddressId")
356
+
357
+ # 是否可拆分
358
+ can_split: Optional[bool] = Field(..., alias="canSplit")
359
+
360
+ # 追踪号
361
+ tracking_no: Optional[str] = Field(None, alias="trackingNo")
362
+
363
+ # 物流方式
364
+ shipping_method: str = Field(..., alias="shippingMethod")
365
+
366
+ # 标签列表
367
+ tag_list: Optional[list[str]] = Field(None, alias="tagList")
368
+
369
+ # 新标签列表
370
+ new_tag_list: Optional[list[dict]] = Field(None, alias="newTagList")
371
+
372
+ # 拆单编号
373
+ split_no: int = Field(..., alias="splitNo")
374
+
375
+ # 是否拆单
376
+ is_split: int = Field(..., alias="isSplit")
377
+
378
+ # ERP 是否标记发货
379
+ is_erp_mark_ship: int = Field(..., alias="isErpMarkShip")
380
+
381
+ # 标签地址
382
+ shipping_label_url: Optional[str] = Field(None, alias="shippingLabelUrl")
383
+
384
+ # 配送渠道类型
385
+ fulfillment_channel_type: int = Field(..., alias="fulfillmentChannelType")
386
+
387
+ # 标签打印时间
388
+ print_label_time: Optional[str] = Field(None, alias="printLabelTime")
389
+
390
+ # 拣货单打印时间
391
+ print_pick_time: Optional[str] = Field(None, alias="printPickTime")
392
+
393
+ # 装箱单打印时间
394
+ print_packing_slip_time: Optional[str] = Field(None, alias="printPackingSlipTime")
395
+
396
+ # 是否 17 物流挂号
397
+ is_register17: int = Field(..., alias="isRegister17")
398
+
399
+ # 第三方仓状态
400
+ third_party_warehouse_status: Optional[str] = Field(None, alias="thirdPartyWarehouseStatus")
401
+
402
+ # 订单类型
403
+ order_type: int = Field(..., alias="orderType")
404
+
405
+ # 被合并订单列表
406
+ be_merged_order_list: Optional[list[dict]] = Field(None, alias="beMergedOrderList")
407
+
408
+ # 买家申请取消
409
+ is_buyer_requested_cancel: int = Field(..., alias="isBuyerRequestedCancel")
410
+
411
+ # 是否作废
412
+ is_voided: int = Field(..., alias="isVoided")
413
+
414
+ # 地址信息
415
+ order_address_vo: Optional[FourSellerOrderAddressVo] = Field(..., alias="orderAddressVo")
416
+
417
+ # 时间信息
418
+ time_info: FourSellerTimeInfoVO = Field(..., alias="timeInfo")
419
+
420
+ # 包裹信息
421
+ package_info: FourSellerPackageInfoVO = Field(..., alias="packageInfo")
422
+
423
+ # 物流服务信息
424
+ shipping_service_info: FourSellerShippingServiceInfoVO = Field(..., alias="shippingServiceInfo")
425
+
426
+ # 商品列表
427
+ order_item_info_list: list[FourSellerOrderItemInfoVO] = Field(..., alias="orderItemInfoList")
428
+
429
+ # 是否样品订单
430
+ is_sample_order: int = Field(..., alias="isSampleOrder")
431
+
432
+ # 是否预售
433
+ pre_sale: Optional[int] = Field(..., alias="preSale")
434
+
435
+ model_config = ConfigDict(
436
+ populate_by_name=True
437
+ )
@@ -0,0 +1,12 @@
1
+ from typing import Generic, TypeVar
2
+ from pydantic import BaseModel
3
+
4
+ T = TypeVar('T')
5
+
6
+
7
+ class Page(Generic[T], BaseModel):
8
+ records: list[T]
9
+ page_number: int
10
+ page_size: int
11
+ total: int
12
+ total_page: int
@@ -0,0 +1,6 @@
1
+ async def safe_close(obj):
2
+ if obj is not None:
3
+ try:
4
+ await obj.close()
5
+ except Exception:
6
+ pass
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ey-commerce-lib
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: eeyoung电商客户端调用封装
5
5
  Author-email: 饶奇奇 <1124393197@qq.com>
6
6
  Requires-Python: >=3.10
7
+ Requires-Dist: ddddocr>=1.5.6
7
8
  Requires-Dist: httpx>=0.28.1
8
9
  Requires-Dist: lxml>=5.3.2
10
+ Requires-Dist: playwright>=1.52.0
9
11
  Requires-Dist: pydantic>=2.11.3
10
12
  Requires-Dist: pytest-asyncio>=0.26.0
11
13
  Requires-Dist: pytest>=8.3.5
@@ -1,4 +1,5 @@
1
1
  ey_commerce_lib/__init__.py,sha256=QTYqXqSTHFRkM9TEgpDFcHvwLbvqHDqvqfQ9EiXkcAM,23
2
+ ey_commerce_lib/model.py,sha256=uL_xo6Zi2B4t44UB0HALv8On3NIV9cgHay00F_Gkecs,230
2
3
  ey_commerce_lib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
4
  ey_commerce_lib/dxm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
5
  ey_commerce_lib/dxm/main.py,sha256=GeUqUoOV9CMBjMucoIWQNhF-Idi2JSk9J0DF8M-AyJ0,21033
@@ -16,10 +17,22 @@ ey_commerce_lib/dxm/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
16
17
  ey_commerce_lib/dxm/schemas/common.py,sha256=ihCeYrh4K_-m9_4rVzHm-o8rFNqzcD5XkO0JQd2023g,234
17
18
  ey_commerce_lib/dxm/schemas/order.py,sha256=uzhSgW0d7Z3L1yYb0meZ2AEF1OosnVM408W2-wZVriE,7802
18
19
  ey_commerce_lib/dxm/schemas/warehouse.py,sha256=kHxNMgkVHCILfAWuDhLN0Q5pDtb3Nd5qexnJowCSnrI,5082
20
+ ey_commerce_lib/four_seller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ ey_commerce_lib/four_seller/main.py,sha256=Mz_Z0Vs-v0UmegOU_lo8eIpXmrWcL7_paRgBrm1s9_o,13131
22
+ ey_commerce_lib/four_seller/constant/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ ey_commerce_lib/four_seller/constant/response.py,sha256=UqGeRCaAV6B_IyNfJR5oJvbKW4BUF5jQGhU5JYpYP1k,163
24
+ ey_commerce_lib/four_seller/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ ey_commerce_lib/four_seller/parser/order.py,sha256=Od8s86eQmq6LzB4nqOWKgF1jN4p2HeyNpBQNYZQ_Sf4,619
26
+ ey_commerce_lib/four_seller/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ ey_commerce_lib/four_seller/schemas/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ ey_commerce_lib/four_seller/schemas/query/order.py,sha256=XV7by4RT9NWvei4C__cQsLTjMMZKLbK73N1GAU7RL7o,3714
29
+ ey_commerce_lib/four_seller/schemas/vo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ ey_commerce_lib/four_seller/schemas/vo/order.py,sha256=aNpnXSaXeUVbBz_7JtP8p6mWIWgUwejUhtYoa3gf4Po,12133
19
31
  ey_commerce_lib/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ ey_commerce_lib/utils/close.py,sha256=-De_H1I-gryytKYhLMsC3HfW67W852XkP1ckK2gLsFs,141
20
33
  ey_commerce_lib/utils/dxm.py,sha256=jVNltK_Pm_yMzXReD0Aw5VW6kzIZ5Bn23RucS0DKBI0,1196
21
34
  ey_commerce_lib/utils/list_util.py,sha256=R1w7B1m3sEXr38zSHWp-15C3xAs5ykYCCpvwmnRW4xs,545
22
35
  ey_commerce_lib/utils/str.py,sha256=939xE0y8U7KEWjwbEezMlaWJNBsfb2BSb-dBpYbOD8Q,138
23
- ey_commerce_lib-1.0.6.dist-info/METADATA,sha256=g-djWVYXp7FIvhyeBL04rw_6qJsX4LYf7tQvt2ovCZ8,326
24
- ey_commerce_lib-1.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- ey_commerce_lib-1.0.6.dist-info/RECORD,,
36
+ ey_commerce_lib-1.0.7.dist-info/METADATA,sha256=duPYCCK5-AgFPfWgoizwIJEQKmaz15fdTy6U_sH0Bzw,390
37
+ ey_commerce_lib-1.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
+ ey_commerce_lib-1.0.7.dist-info/RECORD,,