py_wisharetec_toolkit 3.0.2__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.
@@ -0,0 +1,2218 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+
4
+ """Shanshu HTTP 客户端。
5
+
6
+ 基于 httpx 实现,使用 pydantic 对初始化参数进行验证,
7
+ 登录返回使用 ``models`` 中的 pydantic 模型处理。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ import httpx
15
+ from pydantic import BaseModel, Field, field_validator
16
+
17
+ from .auth import LOGIN_PATH, ShanshuMemoryTokenCache, ShanshuTokenCache, cache_key, md5
18
+ from .models import (
19
+ ShanshuApprovalDetailRecord,
20
+ ShanshuApprovalPageResponse,
21
+ ShanshuArchivePageResponse,
22
+ ShanshuBuildingPageResponse,
23
+ ShanshuCarManagePageResponse,
24
+ ShanshuCommunityPageResponse,
25
+ ShanshuLoginResponse,
26
+ ShanshuMallOrderPageResponse,
27
+ ShanshuOwnerDetailRecord,
28
+ ShanshuPageQueryParams,
29
+ ShanshuParkingPageResponse,
30
+ ShanshuPropertyPageResponse,
31
+ ShanshuResidentPageResponse,
32
+ ShanshuServiceOrderPageResponse,
33
+ ShanshuLoginError,
34
+ ShanshuWorkOrderPageResponse,
35
+ ShanshuYardArchivePageResponse,
36
+ )
37
+
38
+
39
+ DEFAULT_BASE_URL = "https://saas.wisharetec.com/"
40
+
41
+ COMMUNITY_FRONT_PAGE_PATH = "api/space/community/v2/frontPage"
42
+ PROPERTY_PAGE_PATH = "api/space/property/propertyPage"
43
+ YARD_ARCHIVE_PAGE_PATH = "api/space/space/yardArchivePage"
44
+ BUILDING_PAGE_PATH = "api/space/property/getSpaceBuildingPage"
45
+ RESIDENT_PAGE_PATH = "api/user/residentInfo/getResidentPage"
46
+ ARCHIVE_PAGE_PATH = "api/space/archive/examine/page"
47
+ CAR_MANAGE_PAGE_PATH = "api/space/carManage/pageCarManage"
48
+ PARKING_PAGE_PATH = "api/space/parking/parkingPage"
49
+ WORK_ORDER_PAGE_PATH = "api/woreport/task/query/page"
50
+ MALL_ORDER_PAGE_PATH = "api/retailmall/order/pageFront"
51
+ SERVICE_ORDER_PAGE_PATH = "api/houseserve/order/pageFront"
52
+ OWNER_DETAIL_PATH = "api/space/archive/archive/detailSensitive"
53
+ APPROVAL_PAGE_PATH = "api/bpm/process/approval"
54
+ APPROVAL_DETAIL_PATH = "api/bpm/process/progress/{instance_id}/node_433279443693"
55
+
56
+
57
+ class ShanshuClientConfig(BaseModel):
58
+ """Shanshu 客户端配置,所有字段均通过 pydantic 校验。"""
59
+
60
+ base_url: str = Field(default=DEFAULT_BASE_URL)
61
+ account: str = Field(min_length=1)
62
+ password: str = Field(min_length=1)
63
+
64
+ @field_validator("base_url")
65
+ @classmethod
66
+ def _validate_base_url(cls, v: str) -> str:
67
+ if not v or not v.strip():
68
+ raise ValueError("base_url 不能为空")
69
+ v = v.strip()
70
+ if not v.endswith("/"):
71
+ v = v + "/"
72
+ return v
73
+
74
+ @field_validator("account", "password")
75
+ @classmethod
76
+ def _validate_not_empty(cls, v: str) -> str:
77
+ if not v or not v.strip():
78
+ raise ValueError("字段不能为空")
79
+ return v.strip()
80
+
81
+
82
+ class ShanshuClient:
83
+ """基于 httpx 的 Shanshu API 客户端。
84
+
85
+ 参数
86
+ ----------
87
+ base_url : str, optional
88
+ API 基础地址,默认 ``https://saas.wisharetec.com/``。
89
+ account : str
90
+ 账号,用于请求认证头。
91
+ password : str
92
+ 密码,用于请求认证头。
93
+ config : ShanshuClientConfig, optional
94
+ 已构建的配置对象,提供后则 ``base_url``/``account``/``password`` 被忽略。
95
+ timeout : float, optional
96
+ 请求超时时间(秒),默认 30.0。
97
+ token_cache : ShanshuTokenCache, optional
98
+ Token 缓存实现,未提供则使用进程内内存缓存。
99
+ **kwargs : dict
100
+ 透传给 ``httpx.Client`` 的其他参数。
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ base_url: str = DEFAULT_BASE_URL,
106
+ account: str = "",
107
+ password: str = "",
108
+ *,
109
+ config: ShanshuClientConfig | None = None,
110
+ timeout: float = 30.0,
111
+ token_cache: ShanshuTokenCache | None = None,
112
+ **kwargs: Any,
113
+ ) -> None:
114
+ if config is not None:
115
+ self.config = config
116
+ else:
117
+ self.config = ShanshuClientConfig(
118
+ base_url=base_url,
119
+ account=account,
120
+ password=password,
121
+ )
122
+
123
+ self._token_cache: ShanshuTokenCache = token_cache or ShanshuMemoryTokenCache()
124
+ self._token: str | None = None
125
+
126
+ self._client = httpx.Client(
127
+ base_url=self.config.base_url,
128
+ timeout=timeout,
129
+ **kwargs,
130
+ )
131
+
132
+ self._client.headers.update(
133
+ {
134
+ "X-Shanshu-Account": self.config.account,
135
+ "X-Shanshu-Password": self.config.password,
136
+ }
137
+ )
138
+
139
+ @property
140
+ def base_url(self) -> str:
141
+ """客户端基础地址。"""
142
+ return self.config.base_url
143
+
144
+ @property
145
+ def account(self) -> str:
146
+ """客户端账号。"""
147
+ return self.config.account
148
+
149
+ @property
150
+ def token(self) -> str | None:
151
+ """当前缓存的 Token(优先返回实例内存,其次读取缓存后端)。"""
152
+ if self._token is not None:
153
+ return self._token
154
+ key = cache_key(self.config.account)
155
+ return self._token_cache.get(key)
156
+
157
+ def request(
158
+ self,
159
+ method: str,
160
+ url: str,
161
+ *,
162
+ params: dict[str, Any] | None = None,
163
+ json: Any = None,
164
+ data: Any = None,
165
+ headers: dict[str, str] | None = None,
166
+ **kwargs: Any,
167
+ ) -> httpx.Response:
168
+ """发起一次 HTTP 请求。"""
169
+ return self._client.request(
170
+ method=method,
171
+ url=url,
172
+ params=params,
173
+ json=json,
174
+ data=data,
175
+ headers=headers,
176
+ **kwargs,
177
+ )
178
+
179
+ def get(self, url: str, **kwargs: Any) -> httpx.Response:
180
+ """发起 GET 请求。"""
181
+ return self.request("GET", url, **kwargs)
182
+
183
+ def post(self, url: str, **kwargs: Any) -> httpx.Response:
184
+ """发起 POST 请求。"""
185
+ return self.request("POST", url, **kwargs)
186
+
187
+ def login(
188
+ self,
189
+ account: str,
190
+ password: str,
191
+ *,
192
+ password_md5: bool = True,
193
+ token_cache: ShanshuTokenCache | None = None,
194
+ extra_headers: dict[str, str] | None = None,
195
+ ) -> ShanshuLoginResponse:
196
+ """调用 /api/user/loginInteractive 进行交互式登录。
197
+
198
+ 参数
199
+ ----------
200
+ account : str
201
+ 账号。
202
+ password : str
203
+ 明文密码,``password_md5=True`` 时会对其做 MD5 后再发送。
204
+ password_md5 : bool, default True
205
+ 是否对密码做 MD5 加密,默认开启。
206
+ token_cache : ShanshuTokenCache, optional
207
+ 临时覆盖本次登录使用的缓存实现,未提供则使用客户端默认缓存。
208
+ extra_headers : dict, optional
209
+ 额外请求头,会合并到默认头中。
210
+
211
+ 返回
212
+ -------
213
+ ShanshuLoginResponse
214
+ 登录接口的解析后响应(使用 ``models`` 中的 pydantic 模型处理)。
215
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
216
+
217
+ 规则
218
+ -----
219
+ - 请求头固定携带 ``client=co-pc``。
220
+ - 返回体若不包含 ``code`` 字段,视为登录成功。
221
+ - 成功后从响应 headers 中提取 ``authorization`` 值,按
222
+ ``py_wisharetec_toolkit:shanshu:authorization_token:{account}`` 缓存。
223
+ """
224
+ cache = token_cache or self._token_cache
225
+ key = cache_key(account)
226
+
227
+ headers = {"client": "co-pc"}
228
+ if extra_headers:
229
+ headers.update(extra_headers)
230
+
231
+ payload = {
232
+ "account": account,
233
+ "password": md5(password) if password_md5 else password,
234
+ }
235
+
236
+ resp = self._client.post(LOGIN_PATH, json=payload, headers=headers)
237
+
238
+ # 使用 models 中的 pydantic 模型解析返回体
239
+ try:
240
+ body = resp.json()
241
+ except Exception:
242
+ body = None
243
+
244
+ login_resp = ShanshuLoginResponse.model_validate(body)
245
+
246
+ # 同时校验 HTTP 状态码
247
+ resp.raise_for_status()
248
+
249
+ # 登录成功:从响应 headers 提取 authorization token 并按 account 缓存
250
+ token = resp.headers.get("authorization")
251
+ if token:
252
+ cache.set(key, token)
253
+ self._token = token
254
+
255
+ return login_resp
256
+
257
+ def query_community_with_paginator(
258
+ self,
259
+ *,
260
+ params: ShanshuPageQueryParams | None = None,
261
+ extra_headers: dict[str, str] | None = None,
262
+ ) -> ShanshuCommunityPageResponse:
263
+ """调用 /api/space/community/v2/frontPage 查询园区档案列表。
264
+
265
+ 基础档案 -> 园区档案 列表和查询接口。
266
+
267
+ 参数
268
+ ----------
269
+ params : ShanshuPageQueryParams, optional
270
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
271
+ extra_headers : dict, optional
272
+ 额外请求头,会合并到默认头中。
273
+
274
+ 返回
275
+ -------
276
+ ShanshuCommunityPageResponse
277
+ 园区档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
278
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
279
+
280
+ 规则
281
+ -----
282
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
283
+ - 请求方法为 POST,提交参数为 JSON。
284
+ - 返回体不含 ``code`` 字段即视为成功。
285
+ """
286
+ token = self.token
287
+ headers: dict[str, str] = {"client": "co-pc"}
288
+ if token:
289
+ headers["authorization"] = token
290
+ if extra_headers:
291
+ headers.update(extra_headers)
292
+
293
+ page_params = params or ShanshuPageQueryParams()
294
+ payload: dict[str, Any] = {
295
+ "count": page_params.count,
296
+ "last": page_params.last,
297
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
298
+ "pageNum": page_params.page_num,
299
+ "pageSize": page_params.page_size,
300
+ }
301
+ if page_params.conditions is not None:
302
+ payload["conditions"] = page_params.conditions
303
+
304
+ resp = self._client.post(COMMUNITY_FRONT_PAGE_PATH, json=payload, headers=headers)
305
+
306
+ # 使用 models 中的 pydantic 模型解析返回体
307
+ try:
308
+ body = resp.json()
309
+ except Exception:
310
+ body = None
311
+
312
+ community_resp = ShanshuCommunityPageResponse.model_validate(body)
313
+
314
+ # 同时校验 HTTP 状态码
315
+ resp.raise_for_status()
316
+
317
+ return community_resp
318
+
319
+ def query_property_with_paginator(
320
+ self,
321
+ *,
322
+ params: ShanshuPageQueryParams | None = None,
323
+ extra_headers: dict[str, str] | None = None,
324
+ ) -> ShanshuPropertyPageResponse:
325
+ """调用 /api/space/property/propertyPage 查询房产档案列表。
326
+
327
+ 基础档案 -> 房产档案 列表和查询接口。
328
+
329
+ 参数
330
+ ----------
331
+ params : ShanshuPageQueryParams, optional
332
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
333
+ extra_headers : dict, optional
334
+ 额外请求头,会合并到默认头中。
335
+
336
+ 返回
337
+ -------
338
+ ShanshuPropertyPageResponse
339
+ 房产档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
340
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
341
+
342
+ 规则
343
+ -----
344
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
345
+ - 请求方法为 POST,提交参数为 JSON。
346
+ - 返回体不含 ``code`` 字段即视为成功。
347
+ """
348
+ token = self.token
349
+ headers: dict[str, str] = {"client": "co-pc"}
350
+ if token:
351
+ headers["authorization"] = token
352
+ if extra_headers:
353
+ headers.update(extra_headers)
354
+
355
+ page_params = params or ShanshuPageQueryParams()
356
+ payload: dict[str, Any] = {
357
+ "count": page_params.count,
358
+ "last": page_params.last,
359
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
360
+ "pageNum": page_params.page_num,
361
+ "pageSize": page_params.page_size,
362
+ }
363
+ if page_params.conditions is not None:
364
+ payload["conditions"] = page_params.conditions
365
+
366
+ resp = self._client.post(PROPERTY_PAGE_PATH, json=payload, headers=headers)
367
+
368
+ # 使用 models 中的 pydantic 模型解析返回体
369
+ try:
370
+ body = resp.json()
371
+ except Exception:
372
+ body = None
373
+
374
+ property_resp = ShanshuPropertyPageResponse.model_validate(body)
375
+
376
+ # 同时校验 HTTP 状态码
377
+ resp.raise_for_status()
378
+
379
+ return property_resp
380
+
381
+ def query_building_with_paginator(
382
+ self,
383
+ *,
384
+ params: ShanshuPageQueryParams | None = None,
385
+ extra_headers: dict[str, str] | None = None,
386
+ ) -> ShanshuBuildingPageResponse:
387
+ """调用 /api/space/property/getSpaceBuildingPage 查询楼幢档案列表。
388
+
389
+ 基础档案 -> 楼幢档案 列表和查询接口。
390
+
391
+ 参数
392
+ ----------
393
+ params : ShanshuPageQueryParams, optional
394
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
395
+ extra_headers : dict, optional
396
+ 额外请求头,会合并到默认头中。
397
+
398
+ 返回
399
+ -------
400
+ ShanshuBuildingPageResponse
401
+ 楼幢档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
402
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
403
+
404
+ 规则
405
+ -----
406
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
407
+ - 请求方法为 POST,提交参数为 JSON。
408
+ - 返回体不含 ``code`` 字段即视为成功。
409
+ """
410
+ token = self.token
411
+ headers: dict[str, str] = {"client": "co-pc"}
412
+ if token:
413
+ headers["authorization"] = token
414
+ if extra_headers:
415
+ headers.update(extra_headers)
416
+
417
+ page_params = params or ShanshuPageQueryParams()
418
+ payload: dict[str, Any] = {
419
+ "count": page_params.count,
420
+ "last": page_params.last,
421
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
422
+ "pageNum": page_params.page_num,
423
+ "pageSize": page_params.page_size,
424
+ }
425
+ if page_params.conditions is not None:
426
+ payload["conditions"] = page_params.conditions
427
+
428
+ resp = self._client.post(BUILDING_PAGE_PATH, json=payload, headers=headers)
429
+
430
+ # 使用 models 中的 pydantic 模型解析返回体
431
+ try:
432
+ body = resp.json()
433
+ except Exception:
434
+ body = None
435
+
436
+ building_resp = ShanshuBuildingPageResponse.model_validate(body)
437
+
438
+ # 同时校验 HTTP 状态码
439
+ resp.raise_for_status()
440
+
441
+ return building_resp
442
+
443
+ def query_resident_with_paginator(
444
+ self,
445
+ *,
446
+ params: ShanshuPageQueryParams | None = None,
447
+ extra_headers: dict[str, str] | None = None,
448
+ ) -> ShanshuResidentPageResponse:
449
+ """调用 /api/user/residentInfo/getResidentPage 查询用户/居民档案列表。
450
+
451
+ 用户档案 列表和查询接口。
452
+
453
+ 参数
454
+ ----------
455
+ params : ShanshuPageQueryParams, optional
456
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
457
+ extra_headers : dict, optional
458
+ 额外请求头,会合并到默认头中。
459
+
460
+ 返回
461
+ -------
462
+ ShanshuResidentPageResponse
463
+ 用户/居民档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
464
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
465
+
466
+ 规则
467
+ -----
468
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
469
+ - 请求方法为 POST,提交参数为 JSON。
470
+ - 返回体不含 ``code`` 字段即视为成功。
471
+ """
472
+ token = self.token
473
+ headers: dict[str, str] = {"client": "co-pc"}
474
+ if token:
475
+ headers["authorization"] = token
476
+ if extra_headers:
477
+ headers.update(extra_headers)
478
+
479
+ page_params = params or ShanshuPageQueryParams()
480
+ payload: dict[str, Any] = {
481
+ "count": page_params.count,
482
+ "last": page_params.last,
483
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
484
+ "pageNum": page_params.page_num,
485
+ "pageSize": page_params.page_size,
486
+ }
487
+ if page_params.conditions is not None:
488
+ payload["conditions"] = page_params.conditions
489
+
490
+ resp = self._client.post(RESIDENT_PAGE_PATH, json=payload, headers=headers)
491
+
492
+ # 使用 models 中的 pydantic 模型解析返回体
493
+ try:
494
+ body = resp.json()
495
+ except Exception:
496
+ body = None
497
+
498
+ resident_resp = ShanshuResidentPageResponse.model_validate(body)
499
+
500
+ # 同时校验 HTTP 状态码
501
+ resp.raise_for_status()
502
+
503
+ return resident_resp
504
+
505
+ def query_car_manage_with_paginator(
506
+ self,
507
+ *,
508
+ params: ShanshuPageQueryParams | None = None,
509
+ extra_headers: dict[str, str] | None = None,
510
+ ) -> ShanshuCarManagePageResponse:
511
+ """调用 /api/space/carManage/pageCarManage 查询车辆档案列表。
512
+
513
+ 车辆管理 列表和查询接口。
514
+
515
+ 参数
516
+ ----------
517
+ params : ShanshuPageQueryParams, optional
518
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
519
+ extra_headers : dict, optional
520
+ 额外请求头,会合并到默认头中。
521
+
522
+ 返回
523
+ -------
524
+ ShanshuCarManagePageResponse
525
+ 车辆档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
526
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
527
+
528
+ 规则
529
+ -----
530
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
531
+ - 请求方法为 POST,提交参数为 JSON。
532
+ - 返回体不含 ``code`` 字段即视为成功。
533
+ """
534
+ token = self.token
535
+ headers: dict[str, str] = {"client": "co-pc"}
536
+ if token:
537
+ headers["authorization"] = token
538
+ if extra_headers:
539
+ headers.update(extra_headers)
540
+
541
+ page_params = params or ShanshuPageQueryParams()
542
+ payload: dict[str, Any] = {
543
+ "count": page_params.count,
544
+ "last": page_params.last,
545
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
546
+ "pageNum": page_params.page_num,
547
+ "pageSize": page_params.page_size,
548
+ }
549
+ if page_params.conditions is not None:
550
+ payload["conditions"] = page_params.conditions
551
+
552
+ resp = self._client.post(CAR_MANAGE_PAGE_PATH, json=payload, headers=headers)
553
+
554
+ # 使用 models 中的 pydantic 模型解析返回体
555
+ try:
556
+ body = resp.json()
557
+ except Exception:
558
+ body = None
559
+
560
+ car_manage_resp = ShanshuCarManagePageResponse.model_validate(body)
561
+
562
+ # 同时校验 HTTP 状态码
563
+ resp.raise_for_status()
564
+
565
+ return car_manage_resp
566
+
567
+ def query_parking_with_paginator(
568
+ self,
569
+ *,
570
+ params: ShanshuPageQueryParams | None = None,
571
+ extra_headers: dict[str, str] | None = None,
572
+ ) -> ShanshuParkingPageResponse:
573
+ """调用 /api/space/parking/parkingPage 查询车位档案列表。
574
+
575
+ 车位档案 列表和查询接口。
576
+
577
+ 参数
578
+ ----------
579
+ params : ShanshuPageQueryParams, optional
580
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
581
+ extra_headers : dict, optional
582
+ 额外请求头,会合并到默认头中。
583
+
584
+ 返回
585
+ -------
586
+ ShanshuParkingPageResponse
587
+ 车位档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
588
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
589
+
590
+ 规则
591
+ -----
592
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
593
+ - 请求方法为 POST,提交参数为 JSON。
594
+ - 返回体不含 ``code`` 字段即视为成功。
595
+ """
596
+ token = self.token
597
+ headers: dict[str, str] = {"client": "co-pc"}
598
+ if token:
599
+ headers["authorization"] = token
600
+ if extra_headers:
601
+ headers.update(extra_headers)
602
+
603
+ page_params = params or ShanshuPageQueryParams()
604
+ payload: dict[str, Any] = {
605
+ "count": page_params.count,
606
+ "last": page_params.last,
607
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
608
+ "pageNum": page_params.page_num,
609
+ "pageSize": page_params.page_size,
610
+ }
611
+ if page_params.conditions is not None:
612
+ payload["conditions"] = page_params.conditions
613
+
614
+ resp = self._client.post(PARKING_PAGE_PATH, json=payload, headers=headers)
615
+
616
+ # 使用 models 中的 pydantic 模型解析返回体
617
+ try:
618
+ body = resp.json()
619
+ except Exception:
620
+ body = None
621
+
622
+ parking_resp = ShanshuParkingPageResponse.model_validate(body)
623
+
624
+ # 同时校验 HTTP 状态码
625
+ resp.raise_for_status()
626
+
627
+ return parking_resp
628
+
629
+ def query_work_order_with_paginator(
630
+ self,
631
+ *,
632
+ params: ShanshuPageQueryParams | None = None,
633
+ extra_headers: dict[str, str] | None = None,
634
+ ) -> ShanshuWorkOrderPageResponse:
635
+ """调用 /api/woreport/task/query/page 查询工单列表。
636
+
637
+ 工单管理 列表和查询接口。
638
+
639
+ 参数
640
+ ----------
641
+ params : ShanshuPageQueryParams, optional
642
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
643
+ extra_headers : dict, optional
644
+ 额外请求头,会合并到默认头中。
645
+
646
+ 返回
647
+ -------
648
+ ShanshuWorkOrderPageResponse
649
+ 工单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
650
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
651
+
652
+ 规则
653
+ -----
654
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
655
+ - 请求方法为 POST,提交参数为 JSON。
656
+ - 返回体不含 ``code`` 字段即视为成功。
657
+ """
658
+ token = self.token
659
+ headers: dict[str, str] = {"client": "co-pc"}
660
+ if token:
661
+ headers["authorization"] = token
662
+ if extra_headers:
663
+ headers.update(extra_headers)
664
+
665
+ page_params = params or ShanshuPageQueryParams()
666
+ payload: dict[str, Any] = {
667
+ "count": page_params.count,
668
+ "last": page_params.last,
669
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
670
+ "pageNum": page_params.page_num,
671
+ "pageSize": page_params.page_size,
672
+ }
673
+ if page_params.conditions is not None:
674
+ payload["conditions"] = page_params.conditions
675
+
676
+ resp = self._client.post(WORK_ORDER_PAGE_PATH, json=payload, headers=headers)
677
+
678
+ # 使用 models 中的 pydantic 模型解析返回体
679
+ try:
680
+ body = resp.json()
681
+ except Exception:
682
+ body = None
683
+
684
+ work_order_resp = ShanshuWorkOrderPageResponse.model_validate(body)
685
+
686
+ # 同时校验 HTTP 状态码
687
+ resp.raise_for_status()
688
+
689
+ return work_order_resp
690
+
691
+ def query_mall_order_with_paginator(
692
+ self,
693
+ *,
694
+ params: ShanshuPageQueryParams | None = None,
695
+ extra_headers: dict[str, str] | None = None,
696
+ ) -> ShanshuMallOrderPageResponse:
697
+ """调用 /api/retailmall/order/pageFront 查询园区商城订单列表。
698
+
699
+ 商城订单 列表和查询接口。
700
+
701
+ 参数
702
+ ----------
703
+ params : ShanshuPageQueryParams, optional
704
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
705
+ extra_headers : dict, optional
706
+ 额外请求头,会合并到默认头中。
707
+
708
+ 返回
709
+ -------
710
+ ShanshuMallOrderPageResponse
711
+ 商城订单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
712
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
713
+
714
+ 规则
715
+ -----
716
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
717
+ - 请求方法为 POST,提交参数为 JSON。
718
+ - 返回体不含 ``code`` 字段即视为成功。
719
+ """
720
+ token = self.token
721
+ headers: dict[str, str] = {"client": "co-pc"}
722
+ if token:
723
+ headers["authorization"] = token
724
+ if extra_headers:
725
+ headers.update(extra_headers)
726
+
727
+ page_params = params or ShanshuPageQueryParams()
728
+ payload: dict[str, Any] = {
729
+ "count": page_params.count,
730
+ "last": page_params.last,
731
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
732
+ "pageNum": page_params.page_num,
733
+ "pageSize": page_params.page_size,
734
+ }
735
+ if page_params.conditions is not None:
736
+ payload["conditions"] = page_params.conditions
737
+
738
+ resp = self._client.post(MALL_ORDER_PAGE_PATH, json=payload, headers=headers)
739
+
740
+ # 使用 models 中的 pydantic 模型解析返回体
741
+ try:
742
+ body = resp.json()
743
+ except Exception:
744
+ body = None
745
+
746
+ mall_order_resp = ShanshuMallOrderPageResponse.model_validate(body)
747
+
748
+ # 同时校验 HTTP 状态码
749
+ resp.raise_for_status()
750
+
751
+ return mall_order_resp
752
+
753
+ def query_service_order_with_paginator(
754
+ self,
755
+ *,
756
+ params: ShanshuPageQueryParams | None = None,
757
+ extra_headers: dict[str, str] | None = None,
758
+ ) -> ShanshuServiceOrderPageResponse:
759
+ """调用 /api/houseserve/order/pageFront 查询服务订单列表。
760
+
761
+ 服务订单 列表和查询接口。
762
+
763
+ 参数
764
+ ----------
765
+ params : ShanshuPageQueryParams, optional
766
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
767
+ extra_headers : dict, optional
768
+ 额外请求头,会合并到默认头中。
769
+
770
+ 返回
771
+ -------
772
+ ShanshuServiceOrderPageResponse
773
+ 服务订单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
774
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
775
+
776
+ 规则
777
+ -----
778
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
779
+ - 请求方法为 POST,提交参数为 JSON。
780
+ - 返回体不含 ``code`` 字段即视为成功。
781
+ """
782
+ token = self.token
783
+ headers: dict[str, str] = {"client": "co-pc"}
784
+ if token:
785
+ headers["authorization"] = token
786
+ if extra_headers:
787
+ headers.update(extra_headers)
788
+
789
+ page_params = params or ShanshuPageQueryParams()
790
+ payload: dict[str, Any] = {
791
+ "count": page_params.count,
792
+ "last": page_params.last,
793
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
794
+ "pageNum": page_params.page_num,
795
+ "pageSize": page_params.page_size,
796
+ }
797
+ if page_params.conditions is not None:
798
+ payload["conditions"] = page_params.conditions
799
+
800
+ resp = self._client.post(SERVICE_ORDER_PAGE_PATH, json=payload, headers=headers)
801
+
802
+ # 使用 models 中的 pydantic 模型解析返回体
803
+ try:
804
+ body = resp.json()
805
+ except Exception:
806
+ body = None
807
+
808
+ service_order_resp = ShanshuServiceOrderPageResponse.model_validate(body)
809
+
810
+ # 同时校验 HTTP 状态码
811
+ resp.raise_for_status()
812
+
813
+ return service_order_resp
814
+
815
+ def query_approval_with_paginator(
816
+ self,
817
+ *,
818
+ params: ShanshuPageQueryParams | None = None,
819
+ extra_headers: dict[str, str] | None = None,
820
+ ) -> ShanshuApprovalPageResponse:
821
+ """调用 /api/bpm/process/approval 查询审批数据列表。
822
+
823
+ 审批管理 列表和查询接口。
824
+
825
+ 参数
826
+ ----------
827
+ params : ShanshuPageQueryParams, optional
828
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
829
+ extra_headers : dict, optional
830
+ 额外请求头,会合并到默认头中。
831
+
832
+ 返回
833
+ -------
834
+ ShanshuApprovalPageResponse
835
+ 审批数据分页响应(使用 ``models`` 中的 pydantic 模型处理)。
836
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
837
+
838
+ 规则
839
+ -----
840
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
841
+ - 请求方法为 POST,提交参数为 JSON。
842
+ - 返回体不含 ``code`` 字段即视为成功。
843
+ """
844
+ token = self.token
845
+ headers: dict[str, str] = {"client": "co-pc"}
846
+ if token:
847
+ headers["authorization"] = token
848
+ if extra_headers:
849
+ headers.update(extra_headers)
850
+
851
+ page_params = params or ShanshuPageQueryParams()
852
+ payload: dict[str, Any] = {
853
+ "count": page_params.count,
854
+ "last": page_params.last,
855
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
856
+ "pageNum": page_params.page_num,
857
+ "pageSize": page_params.page_size,
858
+ }
859
+ if page_params.conditions is not None:
860
+ payload["conditions"] = page_params.conditions
861
+
862
+ resp = self._client.post(APPROVAL_PAGE_PATH, json=payload, headers=headers)
863
+
864
+ # 使用 models 中的 pydantic 模型解析返回体
865
+ try:
866
+ body = resp.json()
867
+ except Exception:
868
+ body = None
869
+
870
+ approval_resp = ShanshuApprovalPageResponse.model_validate(body)
871
+
872
+ # 同时校验 HTTP 状态码
873
+ resp.raise_for_status()
874
+
875
+ return approval_resp
876
+
877
+ def query_approval_detail(
878
+ self,
879
+ *,
880
+ instance_id: str,
881
+ extra_headers: dict[str, str] | None = None,
882
+ ) -> ShanshuApprovalDetailRecord:
883
+ """调用 /api/bpm/process/progress/{instance_id}/node_433279443693 查询审批详情。
884
+
885
+ 这是一个 GET 请求接口,实例 ID 通过 URL 路径传递,节点 ID 固定为 ``node_433279443693``。
886
+
887
+ 参数
888
+ ----------
889
+ instance_id : str
890
+ 流程实例 ID (procInstId)。
891
+ extra_headers : dict, optional
892
+ 额外请求头,会合并到默认头中。
893
+
894
+ 返回
895
+ -------
896
+ ShanshuApprovalDetailRecord
897
+ 审批详情响应(使用 ``models`` 中的 pydantic 模型处理)。
898
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
899
+
900
+ 规则
901
+ -----
902
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
903
+ - 请求方法为 GET。
904
+ - 返回体不含 ``code`` 字段即视为成功。
905
+ """
906
+ token = self.token
907
+ headers: dict[str, str] = {"client": "co-pc"}
908
+ if token:
909
+ headers["authorization"] = token
910
+ if extra_headers:
911
+ headers.update(extra_headers)
912
+
913
+ url = APPROVAL_DETAIL_PATH.format(instance_id=instance_id)
914
+
915
+ resp = self._client.get(url, headers=headers)
916
+
917
+ # 使用 models 中的 pydantic 模型解析返回体
918
+ try:
919
+ body = resp.json()
920
+ except Exception:
921
+ body = None
922
+
923
+ approval_detail = ShanshuApprovalDetailRecord.model_validate(body)
924
+
925
+ # 同时校验 HTTP 状态码
926
+ resp.raise_for_status()
927
+
928
+ return approval_detail
929
+
930
+ def query_owner_detail(
931
+ self,
932
+ *,
933
+ user_id: str,
934
+ community_id: str,
935
+ space_id: str,
936
+ extra_headers: dict[str, str] | None = None,
937
+ ) -> ShanshuOwnerDetailRecord:
938
+ """调用 /api/space/archive/archive/detailSensitive 查询业主详情。
939
+
940
+ 这是一个 GET 请求接口,参数通过 URL 查询字符串传递。
941
+
942
+ 参数
943
+ ----------
944
+ user_id : str
945
+ 用户 ID。
946
+ community_id : str
947
+ 小区/社区 ID。
948
+ space_id : str
949
+ 空间 ID(房产/车位等空间 ID)。
950
+ extra_headers : dict, optional
951
+ 额外请求头,会合并到默认头中。
952
+
953
+ 返回
954
+ -------
955
+ ShanshuOwnerDetailRecord
956
+ 业主详情响应(使用 ``models`` 中的 pydantic 模型处理)。
957
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
958
+
959
+ 规则
960
+ -----
961
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
962
+ - 请求方法为 GET。
963
+ - 返回体不含 ``code`` 字段即视为成功。
964
+ """
965
+ token = self.token
966
+ headers: dict[str, str] = {"client": "co-pc"}
967
+ if token:
968
+ headers["authorization"] = token
969
+ if extra_headers:
970
+ headers.update(extra_headers)
971
+
972
+ params: dict[str, str] = {
973
+ "userId": user_id,
974
+ "communityId": community_id,
975
+ "spaceId": space_id,
976
+ }
977
+
978
+ resp = self._client.get(OWNER_DETAIL_PATH, params=params, headers=headers)
979
+
980
+ # 使用 models 中的 pydantic 模型解析返回体
981
+ try:
982
+ body = resp.json()
983
+ except Exception:
984
+ body = None
985
+
986
+ owner_detail = ShanshuOwnerDetailRecord.model_validate(body)
987
+
988
+ # 同时校验 HTTP 状态码
989
+ resp.raise_for_status()
990
+
991
+ return owner_detail
992
+
993
+ def query_archive_with_paginator(
994
+ self,
995
+ *,
996
+ params: ShanshuPageQueryParams | None = None,
997
+ extra_headers: dict[str, str] | None = None,
998
+ ) -> ShanshuArchivePageResponse:
999
+ """调用 /api/space/archive/examine/page 查询业主档案列表。
1000
+
1001
+ 业主档案 列表和查询接口。
1002
+
1003
+ 参数
1004
+ ----------
1005
+ params : ShanshuPageQueryParams, optional
1006
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1007
+ extra_headers : dict, optional
1008
+ 额外请求头,会合并到默认头中。
1009
+
1010
+ 返回
1011
+ -------
1012
+ ShanshuArchivePageResponse
1013
+ 业主档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1014
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1015
+
1016
+ 规则
1017
+ -----
1018
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1019
+ - 请求方法为 POST,提交参数为 JSON。
1020
+ - 返回体不含 ``code`` 字段即视为成功。
1021
+ """
1022
+ token = self.token
1023
+ headers: dict[str, str] = {"client": "co-pc"}
1024
+ if token:
1025
+ headers["authorization"] = token
1026
+ if extra_headers:
1027
+ headers.update(extra_headers)
1028
+
1029
+ page_params = params or ShanshuPageQueryParams()
1030
+ payload: dict[str, Any] = {
1031
+ "count": page_params.count,
1032
+ "last": page_params.last,
1033
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1034
+ "pageNum": page_params.page_num,
1035
+ "pageSize": page_params.page_size,
1036
+ }
1037
+ if page_params.conditions is not None:
1038
+ payload["conditions"] = page_params.conditions
1039
+
1040
+ resp = self._client.post(ARCHIVE_PAGE_PATH, json=payload, headers=headers)
1041
+
1042
+ # 使用 models 中的 pydantic 模型解析返回体
1043
+ try:
1044
+ body = resp.json()
1045
+ except Exception:
1046
+ body = None
1047
+
1048
+ archive_resp = ShanshuArchivePageResponse.model_validate(body)
1049
+
1050
+ # 同时校验 HTTP 状态码
1051
+ resp.raise_for_status()
1052
+
1053
+ return archive_resp
1054
+
1055
+ def query_yard_archive_with_paginator(
1056
+ self,
1057
+ *,
1058
+ params: ShanshuPageQueryParams | None = None,
1059
+ extra_headers: dict[str, str] | None = None,
1060
+ ) -> ShanshuYardArchivePageResponse:
1061
+ """调用 /api/space/space/yardArchivePage 查询车场档案列表。
1062
+
1063
+ 基础档案 -> 车场档案 列表和查询接口。
1064
+
1065
+ 参数
1066
+ ----------
1067
+ params : ShanshuPageQueryParams, optional
1068
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1069
+ extra_headers : dict, optional
1070
+ 额外请求头,会合并到默认头中。
1071
+
1072
+ 返回
1073
+ -------
1074
+ ShanshuYardArchivePageResponse
1075
+ 车场档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1076
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1077
+
1078
+ 规则
1079
+ -----
1080
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1081
+ - 请求方法为 POST,提交参数为 JSON。
1082
+ - 返回体不含 ``code`` 字段即视为成功。
1083
+ """
1084
+ token = self.token
1085
+ headers: dict[str, str] = {"client": "co-pc"}
1086
+ if token:
1087
+ headers["authorization"] = token
1088
+ if extra_headers:
1089
+ headers.update(extra_headers)
1090
+
1091
+ page_params = params or ShanshuPageQueryParams()
1092
+ payload: dict[str, Any] = {
1093
+ "count": page_params.count,
1094
+ "last": page_params.last,
1095
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1096
+ "pageNum": page_params.page_num,
1097
+ "pageSize": page_params.page_size,
1098
+ }
1099
+ if page_params.conditions is not None:
1100
+ payload["conditions"] = page_params.conditions
1101
+
1102
+ resp = self._client.post(YARD_ARCHIVE_PAGE_PATH, json=payload, headers=headers)
1103
+
1104
+ # 使用 models 中的 pydantic 模型解析返回体
1105
+ try:
1106
+ body = resp.json()
1107
+ except Exception:
1108
+ body = None
1109
+
1110
+ yard_archive_resp = ShanshuYardArchivePageResponse.model_validate(body)
1111
+
1112
+ # 同时校验 HTTP 状态码
1113
+ resp.raise_for_status()
1114
+
1115
+ return yard_archive_resp
1116
+
1117
+ def close(self) -> None:
1118
+ """关闭底层 httpx 客户端。"""
1119
+ self._client.close()
1120
+
1121
+ def __enter__(self) -> "ShanshuClient":
1122
+ return self
1123
+
1124
+ def __exit__(self, *args: Any) -> None:
1125
+ self.close()
1126
+
1127
+
1128
+ __all__ = [
1129
+ "ShanshuClient",
1130
+ "ShanshuClientConfig",
1131
+ "ShanshuLoginError",
1132
+ "DEFAULT_BASE_URL",
1133
+ "COMMUNITY_FRONT_PAGE_PATH",
1134
+ "AsyncShanshuClient",
1135
+ "AsyncShanshuClientConfig",
1136
+ ]
1137
+
1138
+
1139
+ class AsyncShanshuClientConfig(BaseModel):
1140
+ """Shanshu 异步客户端配置,所有字段均通过 pydantic 校验。"""
1141
+
1142
+ base_url: str = Field(default=DEFAULT_BASE_URL)
1143
+ account: str = Field(min_length=1)
1144
+ password: str = Field(min_length=1)
1145
+
1146
+ @field_validator("base_url")
1147
+ @classmethod
1148
+ def _validate_base_url(cls, v: str) -> str:
1149
+ if not v or not v.strip():
1150
+ raise ValueError("base_url 不能为空")
1151
+ v = v.strip()
1152
+ if not v.endswith("/"):
1153
+ v = v + "/"
1154
+ return v
1155
+
1156
+ @field_validator("account", "password")
1157
+ @classmethod
1158
+ def _validate_not_empty(cls, v: str) -> str:
1159
+ if not v or not v.strip():
1160
+ raise ValueError("字段不能为空")
1161
+ return v.strip()
1162
+
1163
+
1164
+ class AsyncShanshuClient:
1165
+ """基于 httpx.AsyncClient 的 Shanshu 异步 API 客户端。
1166
+
1167
+ 参数
1168
+ ----------
1169
+ base_url : str, optional
1170
+ API 基础地址,默认 ``https://saas.wisharetec.com/``。
1171
+ account : str
1172
+ 账号,用于请求认证头。
1173
+ password : str
1174
+ 密码,用于请求认证头。
1175
+ config : AsyncShanshuClientConfig, optional
1176
+ 已构建的配置对象,提供后则 ``base_url``/``account``/``password`` 被忽略。
1177
+ timeout : float, optional
1178
+ 请求超时时间(秒),默认 30.0。
1179
+ token_cache : ShanshuTokenCache, optional
1180
+ Token 缓存实现,未提供则使用进程内内存缓存。
1181
+ **kwargs : dict
1182
+ 透传给 ``httpx.AsyncClient`` 的其他参数。
1183
+ """
1184
+
1185
+ def __init__(
1186
+ self,
1187
+ base_url: str = DEFAULT_BASE_URL,
1188
+ account: str = "",
1189
+ password: str = "",
1190
+ *,
1191
+ config: AsyncShanshuClientConfig | None = None,
1192
+ timeout: float = 30.0,
1193
+ token_cache: ShanshuTokenCache | None = None,
1194
+ **kwargs: Any,
1195
+ ) -> None:
1196
+ if config is not None:
1197
+ self.config = config
1198
+ else:
1199
+ self.config = AsyncShanshuClientConfig(
1200
+ base_url=base_url,
1201
+ account=account,
1202
+ password=password,
1203
+ )
1204
+
1205
+ self._token_cache: ShanshuTokenCache = token_cache or ShanshuMemoryTokenCache()
1206
+ self._token: str | None = None
1207
+
1208
+ self._client = httpx.AsyncClient(
1209
+ base_url=self.config.base_url,
1210
+ timeout=timeout,
1211
+ **kwargs,
1212
+ )
1213
+
1214
+ self._client.headers.update(
1215
+ {
1216
+ "X-Shanshu-Account": self.config.account,
1217
+ "X-Shanshu-Password": self.config.password,
1218
+ }
1219
+ )
1220
+
1221
+ @property
1222
+ def base_url(self) -> str:
1223
+ """客户端基础地址。"""
1224
+ return self.config.base_url
1225
+
1226
+ @property
1227
+ def account(self) -> str:
1228
+ """客户端账号。"""
1229
+ return self.config.account
1230
+
1231
+ @property
1232
+ def token(self) -> str | None:
1233
+ """当前缓存的 Token(优先返回实例内存,其次读取缓存后端)。"""
1234
+ if self._token is not None:
1235
+ return self._token
1236
+ key = cache_key(self.config.account)
1237
+ return self._token_cache.get(key)
1238
+
1239
+ async def async_request(
1240
+ self,
1241
+ method: str,
1242
+ url: str,
1243
+ *,
1244
+ params: dict[str, Any] | None = None,
1245
+ json: Any = None,
1246
+ data: Any = None,
1247
+ headers: dict[str, str] | None = None,
1248
+ **kwargs: Any,
1249
+ ) -> httpx.Response:
1250
+ """发起一次异步 HTTP 请求。"""
1251
+ return await self._client.request(
1252
+ method=method,
1253
+ url=url,
1254
+ params=params,
1255
+ json=json,
1256
+ data=data,
1257
+ headers=headers,
1258
+ **kwargs,
1259
+ )
1260
+
1261
+ async def async_get(self, url: str, **kwargs: Any) -> httpx.Response:
1262
+ """发起异步 GET 请求。"""
1263
+ return await self.async_request("GET", url, **kwargs)
1264
+
1265
+ async def async_post(self, url: str, **kwargs: Any) -> httpx.Response:
1266
+ """发起异步 POST 请求。"""
1267
+ return await self.async_request("POST", url, **kwargs)
1268
+
1269
+ async def async_login(
1270
+ self,
1271
+ account: str,
1272
+ password: str,
1273
+ *,
1274
+ password_md5: bool = True,
1275
+ token_cache: ShanshuTokenCache | None = None,
1276
+ extra_headers: dict[str, str] | None = None,
1277
+ ) -> ShanshuLoginResponse:
1278
+ """调用 /api/user/loginInteractive 进行交互式登录。
1279
+
1280
+ 参数
1281
+ ----------
1282
+ account : str
1283
+ 账号。
1284
+ password : str
1285
+ 明文密码,``password_md5=True`` 时会对其做 MD5 后再发送。
1286
+ password_md5 : bool, default True
1287
+ 是否对密码做 MD5 加密,默认开启。
1288
+ token_cache : ShanshuTokenCache, optional
1289
+ 临时覆盖本次登录使用的缓存实现,未提供则使用客户端默认缓存。
1290
+ extra_headers : dict, optional
1291
+ 额外请求头,会合并到默认头中。
1292
+
1293
+ 返回
1294
+ -------
1295
+ ShanshuLoginResponse
1296
+ 登录接口的解析后响应(使用 ``models`` 中的 pydantic 模型处理)。
1297
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1298
+
1299
+ 规则
1300
+ -----
1301
+ - 请求头固定携带 ``client=co-pc``。
1302
+ - 返回体若不包含 ``code`` 字段,视为登录成功。
1303
+ - 成功后从响应 headers 中提取 ``authorization`` 值,按
1304
+ ``py_wisharetec_toolkit:shanshu:authorization_token:{account}`` 缓存。
1305
+ """
1306
+ cache = token_cache or self._token_cache
1307
+ key = cache_key(account)
1308
+
1309
+ headers = {"client": "co-pc"}
1310
+ if extra_headers:
1311
+ headers.update(extra_headers)
1312
+
1313
+ payload = {
1314
+ "account": account,
1315
+ "password": md5(password) if password_md5 else password,
1316
+ }
1317
+
1318
+ resp = await self._client.post(LOGIN_PATH, json=payload, headers=headers)
1319
+
1320
+ # 使用 models 中的 pydantic 模型解析返回体
1321
+ try:
1322
+ body = resp.json()
1323
+ except Exception:
1324
+ body = None
1325
+
1326
+ login_resp = ShanshuLoginResponse.model_validate(body)
1327
+
1328
+ # 同时校验 HTTP 状态码
1329
+ resp.raise_for_status()
1330
+
1331
+ # 登录成功:从响应 headers 提取 authorization token 并按 account 缓存
1332
+ token = resp.headers.get("authorization")
1333
+ if token:
1334
+ cache.set(key, token)
1335
+ self._token = token
1336
+
1337
+ return login_resp
1338
+
1339
+ async def async_query_community_with_paginator(
1340
+ self,
1341
+ *,
1342
+ params: ShanshuPageQueryParams | None = None,
1343
+ extra_headers: dict[str, str] | None = None,
1344
+ ) -> ShanshuCommunityPageResponse:
1345
+ """调用 /api/space/community/v2/frontPage 查询园区档案列表。
1346
+
1347
+ 基础档案 -> 园区档案 列表和查询接口。
1348
+
1349
+ 参数
1350
+ ----------
1351
+ params : ShanshuPageQueryParams, optional
1352
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1353
+ extra_headers : dict, optional
1354
+ 额外请求头,会合并到默认头中。
1355
+
1356
+ 返回
1357
+ -------
1358
+ ShanshuCommunityPageResponse
1359
+ 园区档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1360
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1361
+
1362
+ 规则
1363
+ -----
1364
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1365
+ - 请求方法为 POST,提交参数为 JSON。
1366
+ - 返回体不含 ``code`` 字段即视为成功。
1367
+ """
1368
+ token = self.token
1369
+ headers: dict[str, str] = {"client": "co-pc"}
1370
+ if token:
1371
+ headers["authorization"] = token
1372
+ if extra_headers:
1373
+ headers.update(extra_headers)
1374
+
1375
+ page_params = params or ShanshuPageQueryParams()
1376
+ payload: dict[str, Any] = {
1377
+ "count": page_params.count,
1378
+ "last": page_params.last,
1379
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1380
+ "pageNum": page_params.page_num,
1381
+ "pageSize": page_params.page_size,
1382
+ }
1383
+ if page_params.conditions is not None:
1384
+ payload["conditions"] = page_params.conditions
1385
+
1386
+ resp = await self._client.post(COMMUNITY_FRONT_PAGE_PATH, json=payload, headers=headers)
1387
+
1388
+ # 使用 models 中的 pydantic 模型解析返回体
1389
+ try:
1390
+ body = resp.json()
1391
+ except Exception:
1392
+ body = None
1393
+
1394
+ community_resp = ShanshuCommunityPageResponse.model_validate(body)
1395
+
1396
+ # 同时校验 HTTP 状态码
1397
+ resp.raise_for_status()
1398
+
1399
+ return community_resp
1400
+
1401
+ async def async_query_property_with_paginator(
1402
+ self,
1403
+ *,
1404
+ params: ShanshuPageQueryParams | None = None,
1405
+ extra_headers: dict[str, str] | None = None,
1406
+ ) -> ShanshuPropertyPageResponse:
1407
+ """调用 /api/space/property/propertyPage 查询房产档案列表。
1408
+
1409
+ 基础档案 -> 房产档案 列表和查询接口。
1410
+
1411
+ 参数
1412
+ ----------
1413
+ params : ShanshuPageQueryParams, optional
1414
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1415
+ extra_headers : dict, optional
1416
+ 额外请求头,会合并到默认头中。
1417
+
1418
+ 返回
1419
+ -------
1420
+ ShanshuPropertyPageResponse
1421
+ 房产档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1422
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1423
+
1424
+ 规则
1425
+ -----
1426
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1427
+ - 请求方法为 POST,提交参数为 JSON。
1428
+ - 返回体不含 ``code`` 字段即视为成功。
1429
+ """
1430
+ token = self.token
1431
+ headers: dict[str, str] = {"client": "co-pc"}
1432
+ if token:
1433
+ headers["authorization"] = token
1434
+ if extra_headers:
1435
+ headers.update(extra_headers)
1436
+
1437
+ page_params = params or ShanshuPageQueryParams()
1438
+ payload: dict[str, Any] = {
1439
+ "count": page_params.count,
1440
+ "last": page_params.last,
1441
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1442
+ "pageNum": page_params.page_num,
1443
+ "pageSize": page_params.page_size,
1444
+ }
1445
+ if page_params.conditions is not None:
1446
+ payload["conditions"] = page_params.conditions
1447
+
1448
+ resp = await self._client.post(PROPERTY_PAGE_PATH, json=payload, headers=headers)
1449
+
1450
+ # 使用 models 中的 pydantic 模型解析返回体
1451
+ try:
1452
+ body = resp.json()
1453
+ except Exception:
1454
+ body = None
1455
+
1456
+ property_resp = ShanshuPropertyPageResponse.model_validate(body)
1457
+
1458
+ # 同时校验 HTTP 状态码
1459
+ resp.raise_for_status()
1460
+
1461
+ return property_resp
1462
+
1463
+ async def async_query_building_with_paginator(
1464
+ self,
1465
+ *,
1466
+ params: ShanshuPageQueryParams | None = None,
1467
+ extra_headers: dict[str, str] | None = None,
1468
+ ) -> ShanshuBuildingPageResponse:
1469
+ """调用 /api/space/property/getSpaceBuildingPage 查询楼幢档案列表。
1470
+
1471
+ 基础档案 -> 楼幢档案 列表和查询接口。
1472
+
1473
+ 参数
1474
+ ----------
1475
+ params : ShanshuPageQueryParams, optional
1476
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1477
+ extra_headers : dict, optional
1478
+ 额外请求头,会合并到默认头中。
1479
+
1480
+ 返回
1481
+ -------
1482
+ ShanshuBuildingPageResponse
1483
+ 楼幢档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1484
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1485
+
1486
+ 规则
1487
+ -----
1488
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1489
+ - 请求方法为 POST,提交参数为 JSON。
1490
+ - 返回体不含 ``code`` 字段即视为成功。
1491
+ """
1492
+ token = self.token
1493
+ headers: dict[str, str] = {"client": "co-pc"}
1494
+ if token:
1495
+ headers["authorization"] = token
1496
+ if extra_headers:
1497
+ headers.update(extra_headers)
1498
+
1499
+ page_params = params or ShanshuPageQueryParams()
1500
+ payload: dict[str, Any] = {
1501
+ "count": page_params.count,
1502
+ "last": page_params.last,
1503
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1504
+ "pageNum": page_params.page_num,
1505
+ "pageSize": page_params.page_size,
1506
+ }
1507
+ if page_params.conditions is not None:
1508
+ payload["conditions"] = page_params.conditions
1509
+
1510
+ resp = await self._client.post(BUILDING_PAGE_PATH, json=payload, headers=headers)
1511
+
1512
+ # 使用 models 中的 pydantic 模型解析返回体
1513
+ try:
1514
+ body = resp.json()
1515
+ except Exception:
1516
+ body = None
1517
+
1518
+ building_resp = ShanshuBuildingPageResponse.model_validate(body)
1519
+
1520
+ # 同时校验 HTTP 状态码
1521
+ resp.raise_for_status()
1522
+
1523
+ return building_resp
1524
+
1525
+ async def async_query_resident_with_paginator(
1526
+ self,
1527
+ *,
1528
+ params: ShanshuPageQueryParams | None = None,
1529
+ extra_headers: dict[str, str] | None = None,
1530
+ ) -> ShanshuResidentPageResponse:
1531
+ """调用 /api/user/residentInfo/getResidentPage 查询用户/居民档案列表。
1532
+
1533
+ 用户档案 列表和查询接口。
1534
+
1535
+ 参数
1536
+ ----------
1537
+ params : ShanshuPageQueryParams, optional
1538
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1539
+ extra_headers : dict, optional
1540
+ 额外请求头,会合并到默认头中。
1541
+
1542
+ 返回
1543
+ -------
1544
+ ShanshuResidentPageResponse
1545
+ 用户/居民档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1546
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1547
+
1548
+ 规则
1549
+ -----
1550
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1551
+ - 请求方法为 POST,提交参数为 JSON。
1552
+ - 返回体不含 ``code`` 字段即视为成功。
1553
+ """
1554
+ token = self.token
1555
+ headers: dict[str, str] = {"client": "co-pc"}
1556
+ if token:
1557
+ headers["authorization"] = token
1558
+ if extra_headers:
1559
+ headers.update(extra_headers)
1560
+
1561
+ page_params = params or ShanshuPageQueryParams()
1562
+ payload: dict[str, Any] = {
1563
+ "count": page_params.count,
1564
+ "last": page_params.last,
1565
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1566
+ "pageNum": page_params.page_num,
1567
+ "pageSize": page_params.page_size,
1568
+ }
1569
+ if page_params.conditions is not None:
1570
+ payload["conditions"] = page_params.conditions
1571
+
1572
+ resp = await self._client.post(RESIDENT_PAGE_PATH, json=payload, headers=headers)
1573
+
1574
+ # 使用 models 中的 pydantic 模型解析返回体
1575
+ try:
1576
+ body = resp.json()
1577
+ except Exception:
1578
+ body = None
1579
+
1580
+ resident_resp = ShanshuResidentPageResponse.model_validate(body)
1581
+
1582
+ # 同时校验 HTTP 状态码
1583
+ resp.raise_for_status()
1584
+
1585
+ return resident_resp
1586
+
1587
+ async def async_query_car_manage_with_paginator(
1588
+ self,
1589
+ *,
1590
+ params: ShanshuPageQueryParams | None = None,
1591
+ extra_headers: dict[str, str] | None = None,
1592
+ ) -> ShanshuCarManagePageResponse:
1593
+ """调用 /api/space/carManage/pageCarManage 查询车辆档案列表。
1594
+
1595
+ 车辆管理 列表和查询接口。
1596
+
1597
+ 参数
1598
+ ----------
1599
+ params : ShanshuPageQueryParams, optional
1600
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1601
+ extra_headers : dict, optional
1602
+ 额外请求头,会合并到默认头中。
1603
+
1604
+ 返回
1605
+ -------
1606
+ ShanshuCarManagePageResponse
1607
+ 车辆档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1608
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1609
+
1610
+ 规则
1611
+ -----
1612
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1613
+ - 请求方法为 POST,提交参数为 JSON。
1614
+ - 返回体不含 ``code`` 字段即视为成功。
1615
+ """
1616
+ token = self.token
1617
+ headers: dict[str, str] = {"client": "co-pc"}
1618
+ if token:
1619
+ headers["authorization"] = token
1620
+ if extra_headers:
1621
+ headers.update(extra_headers)
1622
+
1623
+ page_params = params or ShanshuPageQueryParams()
1624
+ payload: dict[str, Any] = {
1625
+ "count": page_params.count,
1626
+ "last": page_params.last,
1627
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1628
+ "pageNum": page_params.page_num,
1629
+ "pageSize": page_params.page_size,
1630
+ }
1631
+ if page_params.conditions is not None:
1632
+ payload["conditions"] = page_params.conditions
1633
+
1634
+ resp = await self._client.post(CAR_MANAGE_PAGE_PATH, json=payload, headers=headers)
1635
+
1636
+ # 使用 models 中的 pydantic 模型解析返回体
1637
+ try:
1638
+ body = resp.json()
1639
+ except Exception:
1640
+ body = None
1641
+
1642
+ car_manage_resp = ShanshuCarManagePageResponse.model_validate(body)
1643
+
1644
+ # 同时校验 HTTP 状态码
1645
+ resp.raise_for_status()
1646
+
1647
+ return car_manage_resp
1648
+
1649
+ async def async_query_parking_with_paginator(
1650
+ self,
1651
+ *,
1652
+ params: ShanshuPageQueryParams | None = None,
1653
+ extra_headers: dict[str, str] | None = None,
1654
+ ) -> ShanshuParkingPageResponse:
1655
+ """调用 /api/space/parking/parkingPage 查询车位档案列表。
1656
+
1657
+ 车位档案 列表和查询接口。
1658
+
1659
+ 参数
1660
+ ----------
1661
+ params : ShanshuPageQueryParams, optional
1662
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1663
+ extra_headers : dict, optional
1664
+ 额外请求头,会合并到默认头中。
1665
+
1666
+ 返回
1667
+ -------
1668
+ ShanshuParkingPageResponse
1669
+ 车位档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1670
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1671
+
1672
+ 规则
1673
+ -----
1674
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1675
+ - 请求方法为 POST,提交参数为 JSON。
1676
+ - 返回体不含 ``code`` 字段即视为成功。
1677
+ """
1678
+ token = self.token
1679
+ headers: dict[str, str] = {"client": "co-pc"}
1680
+ if token:
1681
+ headers["authorization"] = token
1682
+ if extra_headers:
1683
+ headers.update(extra_headers)
1684
+
1685
+ page_params = params or ShanshuPageQueryParams()
1686
+ payload: dict[str, Any] = {
1687
+ "count": page_params.count,
1688
+ "last": page_params.last,
1689
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1690
+ "pageNum": page_params.page_num,
1691
+ "pageSize": page_params.page_size,
1692
+ }
1693
+ if page_params.conditions is not None:
1694
+ payload["conditions"] = page_params.conditions
1695
+
1696
+ resp = await self._client.post(PARKING_PAGE_PATH, json=payload, headers=headers)
1697
+
1698
+ # 使用 models 中的 pydantic 模型解析返回体
1699
+ try:
1700
+ body = resp.json()
1701
+ except Exception:
1702
+ body = None
1703
+
1704
+ parking_resp = ShanshuParkingPageResponse.model_validate(body)
1705
+
1706
+ # 同时校验 HTTP 状态码
1707
+ resp.raise_for_status()
1708
+
1709
+ return parking_resp
1710
+
1711
+ async def async_query_work_order_with_paginator(
1712
+ self,
1713
+ *,
1714
+ params: ShanshuPageQueryParams | None = None,
1715
+ extra_headers: dict[str, str] | None = None,
1716
+ ) -> ShanshuWorkOrderPageResponse:
1717
+ """调用 /api/woreport/task/query/page 查询工单列表。
1718
+
1719
+ 工单管理 列表和查询接口。
1720
+
1721
+ 参数
1722
+ ----------
1723
+ params : ShanshuPageQueryParams, optional
1724
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1725
+ extra_headers : dict, optional
1726
+ 额外请求头,会合并到默认头中。
1727
+
1728
+ 返回
1729
+ -------
1730
+ ShanshuWorkOrderPageResponse
1731
+ 工单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1732
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1733
+
1734
+ 规则
1735
+ -----
1736
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1737
+ - 请求方法为 POST,提交参数为 JSON。
1738
+ - 返回体不含 ``code`` 字段即视为成功。
1739
+ """
1740
+ token = self.token
1741
+ headers: dict[str, str] = {"client": "co-pc"}
1742
+ if token:
1743
+ headers["authorization"] = token
1744
+ if extra_headers:
1745
+ headers.update(extra_headers)
1746
+
1747
+ page_params = params or ShanshuPageQueryParams()
1748
+ payload: dict[str, Any] = {
1749
+ "count": page_params.count,
1750
+ "last": page_params.last,
1751
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1752
+ "pageNum": page_params.page_num,
1753
+ "pageSize": page_params.page_size,
1754
+ }
1755
+ if page_params.conditions is not None:
1756
+ payload["conditions"] = page_params.conditions
1757
+
1758
+ resp = await self._client.post(WORK_ORDER_PAGE_PATH, json=payload, headers=headers)
1759
+
1760
+ # 使用 models 中的 pydantic 模型解析返回体
1761
+ try:
1762
+ body = resp.json()
1763
+ except Exception:
1764
+ body = None
1765
+
1766
+ work_order_resp = ShanshuWorkOrderPageResponse.model_validate(body)
1767
+
1768
+ # 同时校验 HTTP 状态码
1769
+ resp.raise_for_status()
1770
+
1771
+ return work_order_resp
1772
+
1773
+ async def async_query_mall_order_with_paginator(
1774
+ self,
1775
+ *,
1776
+ params: ShanshuPageQueryParams | None = None,
1777
+ extra_headers: dict[str, str] | None = None,
1778
+ ) -> ShanshuMallOrderPageResponse:
1779
+ """调用 /api/retailmall/order/pageFront 查询园区商城订单列表。
1780
+
1781
+ 商城订单 列表和查询接口。
1782
+
1783
+ 参数
1784
+ ----------
1785
+ params : ShanshuPageQueryParams, optional
1786
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1787
+ extra_headers : dict, optional
1788
+ 额外请求头,会合并到默认头中。
1789
+
1790
+ 返回
1791
+ -------
1792
+ ShanshuMallOrderPageResponse
1793
+ 商城订单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1794
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1795
+
1796
+ 规则
1797
+ -----
1798
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1799
+ - 请求方法为 POST,提交参数为 JSON。
1800
+ - 返回体不含 ``code`` 字段即视为成功。
1801
+ """
1802
+ token = self.token
1803
+ headers: dict[str, str] = {"client": "co-pc"}
1804
+ if token:
1805
+ headers["authorization"] = token
1806
+ if extra_headers:
1807
+ headers.update(extra_headers)
1808
+
1809
+ page_params = params or ShanshuPageQueryParams()
1810
+ payload: dict[str, Any] = {
1811
+ "count": page_params.count,
1812
+ "last": page_params.last,
1813
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1814
+ "pageNum": page_params.page_num,
1815
+ "pageSize": page_params.page_size,
1816
+ }
1817
+ if page_params.conditions is not None:
1818
+ payload["conditions"] = page_params.conditions
1819
+
1820
+ resp = await self._client.post(MALL_ORDER_PAGE_PATH, json=payload, headers=headers)
1821
+
1822
+ # 使用 models 中的 pydantic 模型解析返回体
1823
+ try:
1824
+ body = resp.json()
1825
+ except Exception:
1826
+ body = None
1827
+
1828
+ mall_order_resp = ShanshuMallOrderPageResponse.model_validate(body)
1829
+
1830
+ # 同时校验 HTTP 状态码
1831
+ resp.raise_for_status()
1832
+
1833
+ return mall_order_resp
1834
+
1835
+ async def async_query_service_order_with_paginator(
1836
+ self,
1837
+ *,
1838
+ params: ShanshuPageQueryParams | None = None,
1839
+ extra_headers: dict[str, str] | None = None,
1840
+ ) -> ShanshuServiceOrderPageResponse:
1841
+ """调用 /api/houseserve/order/pageFront 查询服务订单列表。
1842
+
1843
+ 服务订单 列表和查询接口。
1844
+
1845
+ 参数
1846
+ ----------
1847
+ params : ShanshuPageQueryParams, optional
1848
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1849
+ extra_headers : dict, optional
1850
+ 额外请求头,会合并到默认头中。
1851
+
1852
+ 返回
1853
+ -------
1854
+ ShanshuServiceOrderPageResponse
1855
+ 服务订单分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1856
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1857
+
1858
+ 规则
1859
+ -----
1860
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1861
+ - 请求方法为 POST,提交参数为 JSON。
1862
+ - 返回体不含 ``code`` 字段即视为成功。
1863
+ """
1864
+ token = self.token
1865
+ headers: dict[str, str] = {"client": "co-pc"}
1866
+ if token:
1867
+ headers["authorization"] = token
1868
+ if extra_headers:
1869
+ headers.update(extra_headers)
1870
+
1871
+ page_params = params or ShanshuPageQueryParams()
1872
+ payload: dict[str, Any] = {
1873
+ "count": page_params.count,
1874
+ "last": page_params.last,
1875
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1876
+ "pageNum": page_params.page_num,
1877
+ "pageSize": page_params.page_size,
1878
+ }
1879
+ if page_params.conditions is not None:
1880
+ payload["conditions"] = page_params.conditions
1881
+
1882
+ resp = await self._client.post(SERVICE_ORDER_PAGE_PATH, json=payload, headers=headers)
1883
+
1884
+ # 使用 models 中的 pydantic 模型解析返回体
1885
+ try:
1886
+ body = resp.json()
1887
+ except Exception:
1888
+ body = None
1889
+
1890
+ service_order_resp = ShanshuServiceOrderPageResponse.model_validate(body)
1891
+
1892
+ # 同时校验 HTTP 状态码
1893
+ resp.raise_for_status()
1894
+
1895
+ return service_order_resp
1896
+
1897
+ async def async_query_approval_with_paginator(
1898
+ self,
1899
+ *,
1900
+ params: ShanshuPageQueryParams | None = None,
1901
+ extra_headers: dict[str, str] | None = None,
1902
+ ) -> ShanshuApprovalPageResponse:
1903
+ """调用 /api/bpm/process/approval 查询审批数据列表。
1904
+
1905
+ 审批管理 列表和查询接口。
1906
+
1907
+ 参数
1908
+ ----------
1909
+ params : ShanshuPageQueryParams, optional
1910
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
1911
+ extra_headers : dict, optional
1912
+ 额外请求头,会合并到默认头中。
1913
+
1914
+ 返回
1915
+ -------
1916
+ ShanshuApprovalPageResponse
1917
+ 审批数据分页响应(使用 ``models`` 中的 pydantic 模型处理)。
1918
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1919
+
1920
+ 规则
1921
+ -----
1922
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1923
+ - 请求方法为 POST,提交参数为 JSON。
1924
+ - 返回体不含 ``code`` 字段即视为成功。
1925
+ """
1926
+ token = self.token
1927
+ headers: dict[str, str] = {"client": "co-pc"}
1928
+ if token:
1929
+ headers["authorization"] = token
1930
+ if extra_headers:
1931
+ headers.update(extra_headers)
1932
+
1933
+ page_params = params or ShanshuPageQueryParams()
1934
+ payload: dict[str, Any] = {
1935
+ "count": page_params.count,
1936
+ "last": page_params.last,
1937
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
1938
+ "pageNum": page_params.page_num,
1939
+ "pageSize": page_params.page_size,
1940
+ }
1941
+ if page_params.conditions is not None:
1942
+ payload["conditions"] = page_params.conditions
1943
+
1944
+ resp = await self._client.post(APPROVAL_PAGE_PATH, json=payload, headers=headers)
1945
+
1946
+ # 使用 models 中的 pydantic 模型解析返回体
1947
+ try:
1948
+ body = resp.json()
1949
+ except Exception:
1950
+ body = None
1951
+
1952
+ approval_resp = ShanshuApprovalPageResponse.model_validate(body)
1953
+
1954
+ # 同时校验 HTTP 状态码
1955
+ resp.raise_for_status()
1956
+
1957
+ return approval_resp
1958
+
1959
+ async def async_query_approval_detail(
1960
+ self,
1961
+ *,
1962
+ instance_id: str,
1963
+ extra_headers: dict[str, str] | None = None,
1964
+ ) -> ShanshuApprovalDetailRecord:
1965
+ """调用 /api/bpm/process/progress/{instance_id}/node_433279443693 查询审批详情。
1966
+
1967
+ 这是一个 GET 请求接口,实例 ID 通过 URL 路径传递,节点 ID 固定为 ``node_433279443693``。
1968
+
1969
+ 参数
1970
+ ----------
1971
+ instance_id : str
1972
+ 流程实例 ID (procInstId)。
1973
+ extra_headers : dict, optional
1974
+ 额外请求头,会合并到默认头中。
1975
+
1976
+ 返回
1977
+ -------
1978
+ ShanshuApprovalDetailRecord
1979
+ 审批详情响应(使用 ``models`` 中的 pydantic 模型处理)。
1980
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
1981
+
1982
+ 规则
1983
+ -----
1984
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
1985
+ - 请求方法为 GET。
1986
+ - 返回体不含 ``code`` 字段即视为成功。
1987
+ """
1988
+ token = self.token
1989
+ headers: dict[str, str] = {"client": "co-pc"}
1990
+ if token:
1991
+ headers["authorization"] = token
1992
+ if extra_headers:
1993
+ headers.update(extra_headers)
1994
+
1995
+ url = APPROVAL_DETAIL_PATH.format(instance_id=instance_id)
1996
+
1997
+ resp = await self._client.get(url, headers=headers)
1998
+
1999
+ # 使用 models 中的 pydantic 模型解析返回体
2000
+ try:
2001
+ body = resp.json()
2002
+ except Exception:
2003
+ body = None
2004
+
2005
+ approval_detail = ShanshuApprovalDetailRecord.model_validate(body)
2006
+
2007
+ # 同时校验 HTTP 状态码
2008
+ resp.raise_for_status()
2009
+
2010
+ return approval_detail
2011
+
2012
+ async def async_query_owner_detail(
2013
+ self,
2014
+ *,
2015
+ user_id: str,
2016
+ community_id: str,
2017
+ space_id: str,
2018
+ extra_headers: dict[str, str] | None = None,
2019
+ ) -> ShanshuOwnerDetailRecord:
2020
+ """调用 /api/space/archive/archive/detailSensitive 查询业主详情。
2021
+
2022
+ 这是一个 GET 请求接口,参数通过 URL 查询字符串传递。
2023
+
2024
+ 参数
2025
+ ----------
2026
+ user_id : str
2027
+ 用户 ID。
2028
+ community_id : str
2029
+ 小区/社区 ID。
2030
+ space_id : str
2031
+ 空间 ID(房产/车位等空间 ID)。
2032
+ extra_headers : dict, optional
2033
+ 额外请求头,会合并到默认头中。
2034
+
2035
+ 返回
2036
+ -------
2037
+ ShanshuOwnerDetailRecord
2038
+ 业主详情响应(使用 ``models`` 中的 pydantic 模型处理)。
2039
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
2040
+
2041
+ 规则
2042
+ -----
2043
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
2044
+ - 请求方法为 GET。
2045
+ - 返回体不含 ``code`` 字段即视为成功。
2046
+ """
2047
+ token = self.token
2048
+ headers: dict[str, str] = {"client": "co-pc"}
2049
+ if token:
2050
+ headers["authorization"] = token
2051
+ if extra_headers:
2052
+ headers.update(extra_headers)
2053
+
2054
+ params: dict[str, str] = {
2055
+ "userId": user_id,
2056
+ "communityId": community_id,
2057
+ "spaceId": space_id,
2058
+ }
2059
+
2060
+ resp = await self._client.get(OWNER_DETAIL_PATH, params=params, headers=headers)
2061
+
2062
+ # 使用 models 中的 pydantic 模型解析返回体
2063
+ try:
2064
+ body = resp.json()
2065
+ except Exception:
2066
+ body = None
2067
+
2068
+ owner_detail = ShanshuOwnerDetailRecord.model_validate(body)
2069
+
2070
+ # 同时校验 HTTP 状态码
2071
+ resp.raise_for_status()
2072
+
2073
+ return owner_detail
2074
+
2075
+ async def async_query_archive_with_paginator(
2076
+ self,
2077
+ *,
2078
+ params: ShanshuPageQueryParams | None = None,
2079
+ extra_headers: dict[str, str] | None = None,
2080
+ ) -> ShanshuArchivePageResponse:
2081
+ """调用 /api/space/archive/examine/page 查询业主档案列表。
2082
+
2083
+ 业主档案 列表和查询接口。
2084
+
2085
+ 参数
2086
+ ----------
2087
+ params : ShanshuPageQueryParams, optional
2088
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
2089
+ extra_headers : dict, optional
2090
+ 额外请求头,会合并到默认头中。
2091
+
2092
+ 返回
2093
+ -------
2094
+ ShanshuArchivePageResponse
2095
+ 业主档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
2096
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
2097
+
2098
+ 规则
2099
+ -----
2100
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
2101
+ - 请求方法为 POST,提交参数为 JSON。
2102
+ - 返回体不含 ``code`` 字段即视为成功。
2103
+ """
2104
+ token = self.token
2105
+ headers: dict[str, str] = {"client": "co-pc"}
2106
+ if token:
2107
+ headers["authorization"] = token
2108
+ if extra_headers:
2109
+ headers.update(extra_headers)
2110
+
2111
+ page_params = params or ShanshuPageQueryParams()
2112
+ payload: dict[str, Any] = {
2113
+ "count": page_params.count,
2114
+ "last": page_params.last,
2115
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
2116
+ "pageNum": page_params.page_num,
2117
+ "pageSize": page_params.page_size,
2118
+ }
2119
+ if page_params.conditions is not None:
2120
+ payload["conditions"] = page_params.conditions
2121
+
2122
+ resp = await self._client.post(ARCHIVE_PAGE_PATH, json=payload, headers=headers)
2123
+
2124
+ # 使用 models 中的 pydantic 模型解析返回体
2125
+ try:
2126
+ body = resp.json()
2127
+ except Exception:
2128
+ body = None
2129
+
2130
+ archive_resp = ShanshuArchivePageResponse.model_validate(body)
2131
+
2132
+ # 同时校验 HTTP 状态码
2133
+ resp.raise_for_status()
2134
+
2135
+ return archive_resp
2136
+
2137
+ async def async_query_yard_archive_with_paginator(
2138
+ self,
2139
+ *,
2140
+ params: ShanshuPageQueryParams | None = None,
2141
+ extra_headers: dict[str, str] | None = None,
2142
+ ) -> ShanshuYardArchivePageResponse:
2143
+ """调用 /api/space/space/yardArchivePage 查询车场档案列表。
2144
+
2145
+ 基础档案 -> 车场档案 列表和查询接口。
2146
+
2147
+ 参数
2148
+ ----------
2149
+ params : ShanshuPageQueryParams, optional
2150
+ 分页查询参数,包含 conditions、count、last、order_by、page_num、page_size。
2151
+ extra_headers : dict, optional
2152
+ 额外请求头,会合并到默认头中。
2153
+
2154
+ 返回
2155
+ -------
2156
+ ShanshuYardArchivePageResponse
2157
+ 车场档案分页响应(使用 ``models`` 中的 pydantic 模型处理)。
2158
+ 返回体使用 ``models`` 中的 pydantic 模型解析,是否成功可通过 ``ok`` 属性判断。
2159
+
2160
+ 规则
2161
+ -----
2162
+ - 请求头固定携带 ``client=co-pc`` 和缓存的 ``authorization`` token。
2163
+ - 请求方法为 POST,提交参数为 JSON。
2164
+ - 返回体不含 ``code`` 字段即视为成功。
2165
+ """
2166
+ token = self.token
2167
+ headers: dict[str, str] = {"client": "co-pc"}
2168
+ if token:
2169
+ headers["authorization"] = token
2170
+ if extra_headers:
2171
+ headers.update(extra_headers)
2172
+
2173
+ page_params = params or ShanshuPageQueryParams()
2174
+ payload: dict[str, Any] = {
2175
+ "count": page_params.count,
2176
+ "last": page_params.last,
2177
+ "orderBy": page_params.order_by if page_params.order_by is not None else [],
2178
+ "pageNum": page_params.page_num,
2179
+ "pageSize": page_params.page_size,
2180
+ }
2181
+ if page_params.conditions is not None:
2182
+ payload["conditions"] = page_params.conditions
2183
+
2184
+ resp = await self._client.post(YARD_ARCHIVE_PAGE_PATH, json=payload, headers=headers)
2185
+
2186
+ # 使用 models 中的 pydantic 模型解析返回体
2187
+ try:
2188
+ body = resp.json()
2189
+ except Exception:
2190
+ body = None
2191
+
2192
+ yard_archive_resp = ShanshuYardArchivePageResponse.model_validate(body)
2193
+
2194
+ # 同时校验 HTTP 状态码
2195
+ resp.raise_for_status()
2196
+
2197
+ return yard_archive_resp
2198
+
2199
+ async def async_close(self) -> None:
2200
+ """关闭底层 httpx.AsyncClient 客户端。"""
2201
+ await self._client.aclose()
2202
+
2203
+ async def async_aenter__(self) -> "AsyncShanshuClient":
2204
+ return self
2205
+
2206
+ async def async_aexit__(self, *args: Any) -> None:
2207
+ await self.async_close()
2208
+
2209
+
2210
+ __all__ = [
2211
+ "ShanshuClient",
2212
+ "ShanshuClientConfig",
2213
+ "ShanshuLoginError",
2214
+ "DEFAULT_BASE_URL",
2215
+ "COMMUNITY_FRONT_PAGE_PATH",
2216
+ "AsyncShanshuClient",
2217
+ "AsyncShanshuClientConfig",
2218
+ ]