weflow-sdk 0.9.0__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/PKG-INFO +14 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/README.md +13 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/pyproject.toml +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/__init__.py +2 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/client.py +269 -10
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/spec.json +39 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/__init__.py +3 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/api/default_api.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/api_client.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/configuration.py +2 -2
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/exceptions.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/__init__.py +2 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_conflict.py +9 -3
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_deregistered.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_not_registered.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_phase.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_registered.py +9 -3
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_state_view.py +8 -2
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_status.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_wxid_mismatch.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/accounts_list.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/chatlab_header.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/chatlab_member.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/chatlab_message.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/chatlab_messages.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/chatlab_meta.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/contact.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/contacts.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/delete_api_v1_accounts_wxid200_response.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/event_media.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/event_new.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/event_revoke.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/event_sync.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/group_member.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/group_members.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/health.py +1 -1
- weflow_sdk-0.9.2/src/weflow_sdk/generated/weflow_sdk/models/index_progress_dto.py +91 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/media_brief.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/media_envelope.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/media_object.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/message_native.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/messages_native.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/notification_frame.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/page.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/post_api_v1_accounts200_response.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/pull_envelope.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/pull_message.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/pull_sync.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/quote.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/session_chatlab.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/session_native.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/sessions_chatlab.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/sessions_native.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/sync_frame.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/sync_result.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/watermark_entry.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/watermark_value.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/rest.py +1 -1
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/tests/test_behavior.py +122 -7
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/.gitignore +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/LICENSE +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/openapitools.json +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/scripts/regen.py +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/LICENSE +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/api/__init__.py +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/api_response.py +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/py.typed +0 -0
- {weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: weflow-sdk
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Typed Python client for weflow-server's HTTP API (generated models + handwritten behavior layer)
|
|
5
5
|
Project-URL: Homepage, https://github.com/ren-yc/weflow-server
|
|
6
6
|
Project-URL: Repository, https://github.com/ren-yc/weflow-server
|
|
@@ -50,6 +50,19 @@ async def main() -> None:
|
|
|
50
50
|
asyncio.run(main())
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
Need synchronous code? `SyncClient` mirrors `Client` method-for-method and drives its own
|
|
54
|
+
private event-loop thread — it neither hijacks nor requires a loop in your thread:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from weflow_sdk import SyncClient
|
|
58
|
+
|
|
59
|
+
with SyncClient(base_url="http://127.0.0.1:5033", token="your-token") as client:
|
|
60
|
+
client.health()
|
|
61
|
+
sessions = client.list_all_sessions()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`watch_*` returns a plain iterator with the same reconnect-replay semantics as the async side.
|
|
65
|
+
|
|
53
66
|
## Scope
|
|
54
67
|
|
|
55
68
|
The server exposes a strictly read-only surface: accounts, sessions, messages, contacts, group members, SNS timelines, media export, and an SSE push stream. This client covers **all of it except the SNS timelines** — the server has those endpoints, the generated layer does not wrap them yet, so reach for plain HTTP if you need them (the gap is deliberate, not silent: the contract pins the surface this client is tested against). See the server repository for the API reference.
|
|
@@ -28,6 +28,19 @@ async def main() -> None:
|
|
|
28
28
|
asyncio.run(main())
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Need synchronous code? `SyncClient` mirrors `Client` method-for-method and drives its own
|
|
32
|
+
private event-loop thread — it neither hijacks nor requires a loop in your thread:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from weflow_sdk import SyncClient
|
|
36
|
+
|
|
37
|
+
with SyncClient(base_url="http://127.0.0.1:5033", token="your-token") as client:
|
|
38
|
+
client.health()
|
|
39
|
+
sessions = client.list_all_sessions()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`watch_*` returns a plain iterator with the same reconnect-replay semantics as the async side.
|
|
43
|
+
|
|
31
44
|
## Scope
|
|
32
45
|
|
|
33
46
|
The server exposes a strictly read-only surface: accounts, sessions, messages, contacts, group members, SNS timelines, media export, and an SSE push stream. This client covers **all of it except the SNS timelines** — the server has those endpoints, the generated layer does not wrap them yet, so reach for plain HTTP if you need them (the gap is deliberate, not silent: the contract pins the surface this client is tested against). See the server repository for the API reference.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weflow-sdk"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.2"
|
|
8
8
|
description = "Typed Python client for weflow-server's HTTP API (generated models + handwritten behavior layer)"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
@@ -18,10 +18,11 @@ from __future__ import annotations
|
|
|
18
18
|
import asyncio
|
|
19
19
|
import json as _json
|
|
20
20
|
import logging
|
|
21
|
+
import threading
|
|
21
22
|
import urllib.parse
|
|
22
|
-
from collections.abc import AsyncIterator, Callable
|
|
23
|
+
from collections.abc import AsyncIterator, Callable, Coroutine, Iterator
|
|
23
24
|
from dataclasses import dataclass
|
|
24
|
-
from typing import Any
|
|
25
|
+
from typing import Any, TypeVar
|
|
25
26
|
|
|
26
27
|
import httpx
|
|
27
28
|
from pydantic import ValidationError
|
|
@@ -374,7 +375,14 @@ class Client:
|
|
|
374
375
|
return
|
|
375
376
|
if mine.state is gen.AccountStatus.ERROR:
|
|
376
377
|
raise NotReady(timeout, f"error: {mine.error or ''}")
|
|
377
|
-
|
|
378
|
+
if mine.state is gen.AccountStatus.INDEXING and mine.progress is not None:
|
|
379
|
+
p = mine.progress
|
|
380
|
+
last_state = (
|
|
381
|
+
f"indexing ({p.tables_done} / {p.tables_total} tables, "
|
|
382
|
+
f"{p.messages_indexed} messages indexed)"
|
|
383
|
+
)
|
|
384
|
+
else:
|
|
385
|
+
last_state = str(mine.state.value)
|
|
378
386
|
elif listing is not None:
|
|
379
387
|
last_state = "not-registered"
|
|
380
388
|
if asyncio.get_running_loop().time() >= deadline:
|
|
@@ -425,7 +433,7 @@ class Client:
|
|
|
425
433
|
The failure reason (``error``) and the message count live only here;
|
|
426
434
|
``/health`` collapses everything to a scalar phase.
|
|
427
435
|
"""
|
|
428
|
-
listing = self._validate(gen.AccountsList,
|
|
436
|
+
listing = self._validate(gen.AccountsList,
|
|
429
437
|
await self._get_json("/api/v1/accounts", {})
|
|
430
438
|
)
|
|
431
439
|
return listing.accounts
|
|
@@ -504,7 +512,7 @@ class Client:
|
|
|
504
512
|
query["offset"] = str(offset)
|
|
505
513
|
if limit is not None:
|
|
506
514
|
query["limit"] = str(limit)
|
|
507
|
-
return self._validate(gen.PullEnvelope,
|
|
515
|
+
return self._validate(gen.PullEnvelope,
|
|
508
516
|
await self._get_json(
|
|
509
517
|
f"/api/v1/sessions/{_encode_path_segment(talker)}/messages", query
|
|
510
518
|
)
|
|
@@ -565,7 +573,7 @@ class Client:
|
|
|
565
573
|
query["limit"] = str(page_size)
|
|
566
574
|
if keyword is not None:
|
|
567
575
|
query["keyword"] = keyword
|
|
568
|
-
page = self._validate(gen.SessionsNative,
|
|
576
|
+
page = self._validate(gen.SessionsNative,
|
|
569
577
|
await self._get_json("/api/v1/sessions", query)
|
|
570
578
|
)
|
|
571
579
|
count = len(page.sessions)
|
|
@@ -657,7 +665,7 @@ class Client:
|
|
|
657
665
|
query = _message_query_params(
|
|
658
666
|
talker, keyword, start, end, limit, offset, media
|
|
659
667
|
)
|
|
660
|
-
return self._validate(gen.MessagesNative,
|
|
668
|
+
return self._validate(gen.MessagesNative,
|
|
661
669
|
await self._get_json("/api/v1/messages", query)
|
|
662
670
|
)
|
|
663
671
|
|
|
@@ -688,7 +696,7 @@ class Client:
|
|
|
688
696
|
query = _message_query_params(
|
|
689
697
|
talker, keyword, start, end, limit, offset, media
|
|
690
698
|
)
|
|
691
|
-
return self._validate(gen.ChatlabMessages,
|
|
699
|
+
return self._validate(gen.ChatlabMessages,
|
|
692
700
|
await self._get_json("/chatlab/messages", query)
|
|
693
701
|
)
|
|
694
702
|
|
|
@@ -711,7 +719,7 @@ class Client:
|
|
|
711
719
|
query["offset"] = str(offset)
|
|
712
720
|
if keyword is not None:
|
|
713
721
|
query["keyword"] = keyword
|
|
714
|
-
return self._validate(gen.Contacts,
|
|
722
|
+
return self._validate(gen.Contacts,
|
|
715
723
|
await self._get_json("/api/v1/contacts", query)
|
|
716
724
|
)
|
|
717
725
|
|
|
@@ -742,7 +750,7 @@ class Client:
|
|
|
742
750
|
query["includeMessageCounts"] = "1"
|
|
743
751
|
# Counting messages scans every session behind each roster entry: that is
|
|
744
752
|
# exactly the request the published read budget must not apply to.
|
|
745
|
-
return self._validate(gen.GroupMembers,
|
|
753
|
+
return self._validate(gen.GroupMembers,
|
|
746
754
|
await self._get_json(
|
|
747
755
|
"/api/v1/group-members", query, long=include_message_counts
|
|
748
756
|
)
|
|
@@ -947,3 +955,254 @@ class Client:
|
|
|
947
955
|
# server event kind degrades instead of killing the stream.
|
|
948
956
|
log.debug("ignoring unknown SSE event kind: %s", kind)
|
|
949
957
|
return None
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
_T = TypeVar("_T")
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
class SyncClient:
|
|
964
|
+
"""Synchronous facade over :class:`Client` with its own event-loop thread.
|
|
965
|
+
|
|
966
|
+
Why a dedicated thread: a sync caller may already be inside a running
|
|
967
|
+
asyncio loop (Jupyter, an async web handler, a test harness). Calling
|
|
968
|
+
``asyncio.run`` there fails with "cannot be called from a running event
|
|
969
|
+
loop"; creating a loop in the calling thread and closing it later also
|
|
970
|
+
conflicts with that thread's loop. Here every coroutine runs on one
|
|
971
|
+
private loop owned by this object, so the caller's loop is untouched.
|
|
972
|
+
|
|
973
|
+
The method surface mirrors :class:`Client` one-to-one; ``watch`` returns a
|
|
974
|
+
normal iterator that keeps the same reconnect/``Last-Event-ID`` semantics.
|
|
975
|
+
"""
|
|
976
|
+
|
|
977
|
+
def __init__(
|
|
978
|
+
self,
|
|
979
|
+
base_url: str,
|
|
980
|
+
token: str,
|
|
981
|
+
timeout: float = READ_TIMEOUT,
|
|
982
|
+
stall_timeout: float = STALL_TIMEOUT,
|
|
983
|
+
) -> None:
|
|
984
|
+
self._closed = False
|
|
985
|
+
self._loop = asyncio.new_event_loop()
|
|
986
|
+
self._ready = threading.Event()
|
|
987
|
+
self._thread = threading.Thread(
|
|
988
|
+
target=self._run_loop, name="weflow-sync-client", daemon=True
|
|
989
|
+
)
|
|
990
|
+
self._thread.start()
|
|
991
|
+
if not self._ready.wait(timeout=5.0):
|
|
992
|
+
self._stop_loop()
|
|
993
|
+
raise ClientError("SyncClient event loop did not start")
|
|
994
|
+
self._client: Client
|
|
995
|
+
try:
|
|
996
|
+
self._client = self._call(
|
|
997
|
+
self._new_client(base_url, token, timeout, stall_timeout)
|
|
998
|
+
)
|
|
999
|
+
except BaseException:
|
|
1000
|
+
self._stop_loop()
|
|
1001
|
+
raise
|
|
1002
|
+
|
|
1003
|
+
# ---- lifecycle ------------------------------------------------------
|
|
1004
|
+
|
|
1005
|
+
def _run_loop(self) -> None:
|
|
1006
|
+
asyncio.set_event_loop(self._loop)
|
|
1007
|
+
# Set ready from *inside* the loop, so __init__ cannot observe a
|
|
1008
|
+
# running thread whose loop has not started scheduling yet.
|
|
1009
|
+
self._loop.call_soon(self._ready.set)
|
|
1010
|
+
try:
|
|
1011
|
+
self._loop.run_forever()
|
|
1012
|
+
finally:
|
|
1013
|
+
self._loop.close()
|
|
1014
|
+
|
|
1015
|
+
async def _new_client(
|
|
1016
|
+
self, base_url: str, token: str, timeout: float, stall_timeout: float
|
|
1017
|
+
) -> Client:
|
|
1018
|
+
# Construct inside the loop thread: httpx binds its async transport to
|
|
1019
|
+
# the loop that first uses it.
|
|
1020
|
+
return Client(base_url, token, timeout, stall_timeout)
|
|
1021
|
+
|
|
1022
|
+
def _call(self, coro: Coroutine[Any, Any, _T]) -> _T:
|
|
1023
|
+
if self._closed:
|
|
1024
|
+
raise ClientError("SyncClient is closed")
|
|
1025
|
+
if not self._thread.is_alive() or not self._loop.is_running():
|
|
1026
|
+
raise ClientError("SyncClient event loop thread is not running")
|
|
1027
|
+
return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
|
|
1028
|
+
|
|
1029
|
+
def _stop_loop(self) -> None:
|
|
1030
|
+
if self._closed:
|
|
1031
|
+
return
|
|
1032
|
+
self._closed = True
|
|
1033
|
+
if self._thread.is_alive():
|
|
1034
|
+
try:
|
|
1035
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
1036
|
+
except RuntimeError:
|
|
1037
|
+
pass
|
|
1038
|
+
self._thread.join(timeout=5.0)
|
|
1039
|
+
|
|
1040
|
+
def close(self) -> None:
|
|
1041
|
+
"""Close the HTTP client and stop the loop thread (idempotent)."""
|
|
1042
|
+
if self._closed:
|
|
1043
|
+
return
|
|
1044
|
+
try:
|
|
1045
|
+
self._call(self._client.aclose())
|
|
1046
|
+
finally:
|
|
1047
|
+
self._stop_loop()
|
|
1048
|
+
|
|
1049
|
+
def __enter__(self) -> SyncClient:
|
|
1050
|
+
return self
|
|
1051
|
+
|
|
1052
|
+
def __exit__(self, *exc: Any) -> None:
|
|
1053
|
+
self.close()
|
|
1054
|
+
|
|
1055
|
+
# ---- mirrors of Client ----------------------------------------------
|
|
1056
|
+
|
|
1057
|
+
def wait_ready(self, account: str, timeout: float = 120.0) -> None:
|
|
1058
|
+
self._call(self._client.wait_ready(account, timeout))
|
|
1059
|
+
|
|
1060
|
+
def ensure_ready(self, account: str, body: dict, timeout: float = 120.0) -> None:
|
|
1061
|
+
self._call(self._client.ensure_ready(account, body, timeout))
|
|
1062
|
+
|
|
1063
|
+
def health(self) -> gen.Health:
|
|
1064
|
+
return self._call(self._client.health())
|
|
1065
|
+
|
|
1066
|
+
def accounts(self) -> list[gen.AccountStateView]:
|
|
1067
|
+
return self._call(self._client.accounts())
|
|
1068
|
+
|
|
1069
|
+
def register(self, body: dict) -> RegisterOutcome:
|
|
1070
|
+
return self._call(self._client.register(body))
|
|
1071
|
+
|
|
1072
|
+
def sync_now(self) -> gen.SyncResult:
|
|
1073
|
+
return self._call(self._client.sync_now())
|
|
1074
|
+
|
|
1075
|
+
def pull_page(
|
|
1076
|
+
self,
|
|
1077
|
+
talker: str,
|
|
1078
|
+
since: int | None,
|
|
1079
|
+
offset: int = 0,
|
|
1080
|
+
limit: int | None = None,
|
|
1081
|
+
) -> gen.PullEnvelope:
|
|
1082
|
+
return self._call(self._client.pull_page(talker, since, offset, limit))
|
|
1083
|
+
|
|
1084
|
+
def drain_session(
|
|
1085
|
+
self,
|
|
1086
|
+
talker: str,
|
|
1087
|
+
since: int | None,
|
|
1088
|
+
on_page: Callable[[list[gen.PullMessage]], None],
|
|
1089
|
+
) -> int:
|
|
1090
|
+
# Loop in the caller thread so on_page runs where the caller expects
|
|
1091
|
+
# it; the per-page request itself is still submitted to the loop thread.
|
|
1092
|
+
next_since = since
|
|
1093
|
+
next_offset = 0
|
|
1094
|
+
total = 0
|
|
1095
|
+
while True:
|
|
1096
|
+
page = self.pull_page(talker, next_since, next_offset)
|
|
1097
|
+
total += len(page.messages)
|
|
1098
|
+
on_page(page.messages)
|
|
1099
|
+
if not page.sync.has_more:
|
|
1100
|
+
return total
|
|
1101
|
+
next_since = page.sync.next_since
|
|
1102
|
+
next_offset = page.sync.next_offset
|
|
1103
|
+
|
|
1104
|
+
def list_all_sessions(
|
|
1105
|
+
self,
|
|
1106
|
+
*,
|
|
1107
|
+
page_size: int | None = None,
|
|
1108
|
+
keyword: str | None = None,
|
|
1109
|
+
) -> list[gen.SessionNative]:
|
|
1110
|
+
return self._call(self._client.list_all_sessions(page_size=page_size, keyword=keyword))
|
|
1111
|
+
|
|
1112
|
+
def media_bytes(self, message: gen.ChatlabMessage, talker: str) -> bytes:
|
|
1113
|
+
return self._call(self._client.media_bytes(message, talker))
|
|
1114
|
+
|
|
1115
|
+
def list_messages(
|
|
1116
|
+
self,
|
|
1117
|
+
talker: str,
|
|
1118
|
+
*,
|
|
1119
|
+
keyword: str | None = None,
|
|
1120
|
+
start: str | None = None,
|
|
1121
|
+
end: str | None = None,
|
|
1122
|
+
limit: int | None = None,
|
|
1123
|
+
offset: int | None = None,
|
|
1124
|
+
media: bool = False,
|
|
1125
|
+
) -> gen.MessagesNative:
|
|
1126
|
+
return self._call(
|
|
1127
|
+
self._client.list_messages(
|
|
1128
|
+
talker,
|
|
1129
|
+
keyword=keyword,
|
|
1130
|
+
start=start,
|
|
1131
|
+
end=end,
|
|
1132
|
+
limit=limit,
|
|
1133
|
+
offset=offset,
|
|
1134
|
+
media=media,
|
|
1135
|
+
)
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
def chatlab_messages(
|
|
1139
|
+
self,
|
|
1140
|
+
talker: str,
|
|
1141
|
+
*,
|
|
1142
|
+
keyword: str | None = None,
|
|
1143
|
+
start: str | None = None,
|
|
1144
|
+
end: str | None = None,
|
|
1145
|
+
limit: int | None = None,
|
|
1146
|
+
offset: int | None = None,
|
|
1147
|
+
media: bool = False,
|
|
1148
|
+
) -> gen.ChatlabMessages:
|
|
1149
|
+
return self._call(
|
|
1150
|
+
self._client.chatlab_messages(
|
|
1151
|
+
talker,
|
|
1152
|
+
keyword=keyword,
|
|
1153
|
+
start=start,
|
|
1154
|
+
end=end,
|
|
1155
|
+
limit=limit,
|
|
1156
|
+
offset=offset,
|
|
1157
|
+
media=media,
|
|
1158
|
+
)
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
def contacts(
|
|
1162
|
+
self,
|
|
1163
|
+
*,
|
|
1164
|
+
limit: int | None = None,
|
|
1165
|
+
offset: int | None = None,
|
|
1166
|
+
keyword: str | None = None,
|
|
1167
|
+
) -> gen.Contacts:
|
|
1168
|
+
return self._call(self._client.contacts(limit=limit, offset=offset, keyword=keyword))
|
|
1169
|
+
|
|
1170
|
+
def group_members(
|
|
1171
|
+
self,
|
|
1172
|
+
chatroom: str,
|
|
1173
|
+
*,
|
|
1174
|
+
include_message_counts: bool = False,
|
|
1175
|
+
) -> gen.GroupMembers:
|
|
1176
|
+
return self._call(
|
|
1177
|
+
self._client.group_members(
|
|
1178
|
+
chatroom, include_message_counts=include_message_counts
|
|
1179
|
+
)
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
def media_bytes_by_id(self, media_id: str) -> bytes:
|
|
1183
|
+
return self._call(self._client.media_bytes_by_id(media_id))
|
|
1184
|
+
|
|
1185
|
+
def watch(self) -> Iterator[Any]:
|
|
1186
|
+
"""Yield live events, closing the async generator on early exit."""
|
|
1187
|
+
# Annotated as Any only to keep ``aclose`` visible to mypy: the public
|
|
1188
|
+
# Client.watch return type is AsyncIterator, which does not declare it.
|
|
1189
|
+
agen: Any = self._client.watch()
|
|
1190
|
+
|
|
1191
|
+
async def _next() -> Any:
|
|
1192
|
+
return await agen.__anext__()
|
|
1193
|
+
|
|
1194
|
+
async def _aclose() -> None:
|
|
1195
|
+
await agen.aclose()
|
|
1196
|
+
|
|
1197
|
+
try:
|
|
1198
|
+
while True:
|
|
1199
|
+
try:
|
|
1200
|
+
yield self._call(_next())
|
|
1201
|
+
except StopAsyncIteration:
|
|
1202
|
+
return
|
|
1203
|
+
finally:
|
|
1204
|
+
if not self._closed:
|
|
1205
|
+
try:
|
|
1206
|
+
self._call(_aclose())
|
|
1207
|
+
except (StopAsyncIteration, ClientError):
|
|
1208
|
+
pass
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"occupied_status": {
|
|
11
11
|
"$ref": "#/components/schemas/AccountStatus"
|
|
12
12
|
},
|
|
13
|
+
"progress": {
|
|
14
|
+
"$ref": "#/components/schemas/IndexProgressDto",
|
|
15
|
+
"description": "`indexing` 态才有;其余态整块省略(不是 `null`)。"
|
|
16
|
+
},
|
|
13
17
|
"state": {
|
|
14
18
|
"type": "string"
|
|
15
19
|
},
|
|
@@ -116,6 +120,10 @@
|
|
|
116
120
|
"db_storage": {
|
|
117
121
|
"type": "string"
|
|
118
122
|
},
|
|
123
|
+
"progress": {
|
|
124
|
+
"$ref": "#/components/schemas/IndexProgressDto",
|
|
125
|
+
"description": "`indexing` 态才有;`ready` 态整块省略——完成度是 100%,再报一遍只是冗余键。"
|
|
126
|
+
},
|
|
119
127
|
"state": {
|
|
120
128
|
"type": "string"
|
|
121
129
|
},
|
|
@@ -152,6 +160,10 @@
|
|
|
152
160
|
"minimum": 0,
|
|
153
161
|
"type": "integer"
|
|
154
162
|
},
|
|
163
|
+
"progress": {
|
|
164
|
+
"$ref": "#/components/schemas/IndexProgressDto",
|
|
165
|
+
"description": "`indexing` 态才有;其余态整块省略(条件键,与 `error` 同族)。"
|
|
166
|
+
},
|
|
155
167
|
"state": {
|
|
156
168
|
"$ref": "#/components/schemas/AccountStatus"
|
|
157
169
|
},
|
|
@@ -722,6 +734,32 @@
|
|
|
722
734
|
],
|
|
723
735
|
"type": "object"
|
|
724
736
|
},
|
|
737
|
+
"IndexProgressDto": {
|
|
738
|
+
"description": "建索引进度(**只在 `indexing` 态出现**)。\n\n三个键同生同灭:客户端要么一次拿到完整的三个数,要么整块缺失——所以每个承载它的\n响应字段都是 `Option` + `skip_serializing_if`(缺省即没有,不是 `null`)。\n\n分母口径是**消息表**:`tables_total` 只数消息库里的 Msg_* 表。session/contact/sns\n是毫秒级,混进分母会让开跑时的百分比虚高再卡住,反而误导「还要等多久」。\n行数逐行推进;读失败的行不计入。表失败时 `tables_done` 会比 `tables_total` 少一格 ——\n那正是「有一条表确实没读成」的诚实表达。",
|
|
739
|
+
"properties": {
|
|
740
|
+
"messages_indexed": {
|
|
741
|
+
"description": "已成功读入索引的消息行累计值(逐行推进;读失败的行不计)。",
|
|
742
|
+
"minimum": 0,
|
|
743
|
+
"type": "integer"
|
|
744
|
+
},
|
|
745
|
+
"tables_done": {
|
|
746
|
+
"description": "已扫完的消息表数。",
|
|
747
|
+
"minimum": 0,
|
|
748
|
+
"type": "integer"
|
|
749
|
+
},
|
|
750
|
+
"tables_total": {
|
|
751
|
+
"description": "消息表总数(本账号本次构建的枚举结果)。",
|
|
752
|
+
"minimum": 0,
|
|
753
|
+
"type": "integer"
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
"required": [
|
|
757
|
+
"messages_indexed",
|
|
758
|
+
"tables_done",
|
|
759
|
+
"tables_total"
|
|
760
|
+
],
|
|
761
|
+
"type": "object"
|
|
762
|
+
},
|
|
725
763
|
"MediaBrief": {
|
|
726
764
|
"description": "一条消息的媒体**元数据**(拉取面与消息面同形)。\n\n它**不是**「字节可取」的承诺:无媒体时整个键省略;`fileName` 只有在导出确实写出了本地\n副本之后才是可取句柄(那时它是**实际导出文件名**),否则它只是这条消息自带的文件名。\n`md5` 取不到时省略该键 —— 未导出不等于没有摘要。",
|
|
727
765
|
"properties": {
|
|
@@ -1348,7 +1386,7 @@
|
|
|
1348
1386
|
},
|
|
1349
1387
|
"info": {
|
|
1350
1388
|
"title": "weflow-server",
|
|
1351
|
-
"version": "0.9.
|
|
1389
|
+
"version": "0.9.2"
|
|
1352
1390
|
},
|
|
1353
1391
|
"openapi": "3.0.3",
|
|
1354
1392
|
"paths": {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
9
9
|
|
|
10
|
-
The version of the OpenAPI document: 0.9.
|
|
10
|
+
The version of the OpenAPI document: 0.9.2
|
|
11
11
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
12
12
|
|
|
13
13
|
Do not edit the class manually.
|
|
@@ -52,6 +52,7 @@ __all__ = [
|
|
|
52
52
|
"GroupMember",
|
|
53
53
|
"GroupMembers",
|
|
54
54
|
"Health",
|
|
55
|
+
"IndexProgressDto",
|
|
55
56
|
"MediaBrief",
|
|
56
57
|
"MediaEnvelope",
|
|
57
58
|
"MediaObject",
|
|
@@ -113,6 +114,7 @@ from weflow_sdk.generated.weflow_sdk.models.event_sync import EventSync as Event
|
|
|
113
114
|
from weflow_sdk.generated.weflow_sdk.models.group_member import GroupMember as GroupMember
|
|
114
115
|
from weflow_sdk.generated.weflow_sdk.models.group_members import GroupMembers as GroupMembers
|
|
115
116
|
from weflow_sdk.generated.weflow_sdk.models.health import Health as Health
|
|
117
|
+
from weflow_sdk.generated.weflow_sdk.models.index_progress_dto import IndexProgressDto as IndexProgressDto
|
|
116
118
|
from weflow_sdk.generated.weflow_sdk.models.media_brief import MediaBrief as MediaBrief
|
|
117
119
|
from weflow_sdk.generated.weflow_sdk.models.media_envelope import MediaEnvelope as MediaEnvelope
|
|
118
120
|
from weflow_sdk.generated.weflow_sdk.models.media_object import MediaObject as MediaObject
|
{weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/api/default_api.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
5
5
|
|
|
6
|
-
The version of the OpenAPI document: 0.9.
|
|
6
|
+
The version of the OpenAPI document: 0.9.2
|
|
7
7
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
8
8
|
|
|
9
9
|
Do not edit the class manually.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
5
5
|
|
|
6
|
-
The version of the OpenAPI document: 0.9.
|
|
6
|
+
The version of the OpenAPI document: 0.9.2
|
|
7
7
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
8
8
|
|
|
9
9
|
Do not edit the class manually.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
5
5
|
|
|
6
|
-
The version of the OpenAPI document: 0.9.
|
|
6
|
+
The version of the OpenAPI document: 0.9.2
|
|
7
7
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
8
8
|
|
|
9
9
|
Do not edit the class manually.
|
|
@@ -522,7 +522,7 @@ class Configuration:
|
|
|
522
522
|
return "Python SDK Debug Report:\n"\
|
|
523
523
|
"OS: {env}\n"\
|
|
524
524
|
"Python Version: {pyversion}\n"\
|
|
525
|
-
"Version of the API: 0.9.
|
|
525
|
+
"Version of the API: 0.9.2\n"\
|
|
526
526
|
"SDK Package Version: 1.0.0".\
|
|
527
527
|
format(env=sys.platform, pyversion=sys.version)
|
|
528
528
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
5
5
|
|
|
6
|
-
The version of the OpenAPI document: 0.9.
|
|
6
|
+
The version of the OpenAPI document: 0.9.2
|
|
7
7
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
8
8
|
|
|
9
9
|
Do not edit the class manually.
|
{weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/__init__.py
RENAMED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
8
8
|
|
|
9
|
-
The version of the OpenAPI document: 0.9.
|
|
9
|
+
The version of the OpenAPI document: 0.9.2
|
|
10
10
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
11
11
|
|
|
12
12
|
Do not edit the class manually.
|
|
@@ -37,6 +37,7 @@ from weflow_sdk.generated.weflow_sdk.models.event_sync import EventSync
|
|
|
37
37
|
from weflow_sdk.generated.weflow_sdk.models.group_member import GroupMember
|
|
38
38
|
from weflow_sdk.generated.weflow_sdk.models.group_members import GroupMembers
|
|
39
39
|
from weflow_sdk.generated.weflow_sdk.models.health import Health
|
|
40
|
+
from weflow_sdk.generated.weflow_sdk.models.index_progress_dto import IndexProgressDto
|
|
40
41
|
from weflow_sdk.generated.weflow_sdk.models.media_brief import MediaBrief
|
|
41
42
|
from weflow_sdk.generated.weflow_sdk.models.media_envelope import MediaEnvelope
|
|
42
43
|
from weflow_sdk.generated.weflow_sdk.models.media_object import MediaObject
|
{weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_conflict.py
RENAMED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.9.
|
|
8
|
+
The version of the OpenAPI document: 0.9.2
|
|
9
9
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
10
|
|
|
11
11
|
Do not edit the class manually.
|
|
@@ -18,8 +18,9 @@ import re # noqa: F401
|
|
|
18
18
|
import json
|
|
19
19
|
|
|
20
20
|
from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr
|
|
21
|
-
from typing import Any, ClassVar, Dict, List
|
|
21
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
|
22
22
|
from weflow_sdk.generated.weflow_sdk.models.account_status import AccountStatus
|
|
23
|
+
from weflow_sdk.generated.weflow_sdk.models.index_progress_dto import IndexProgressDto
|
|
23
24
|
from typing import Optional, Set
|
|
24
25
|
from typing_extensions import Self
|
|
25
26
|
from pydantic_core import to_jsonable_python
|
|
@@ -30,10 +31,11 @@ class AccountConflict(BaseModel):
|
|
|
30
31
|
""" # noqa: E501
|
|
31
32
|
occupied_by: StrictStr
|
|
32
33
|
occupied_status: AccountStatus
|
|
34
|
+
progress: Optional[IndexProgressDto] = None
|
|
33
35
|
state: StrictStr
|
|
34
36
|
success: StrictBool
|
|
35
37
|
wxid: StrictStr
|
|
36
|
-
__properties: ClassVar[List[str]] = ["occupied_by", "occupied_status", "state", "success", "wxid"]
|
|
38
|
+
__properties: ClassVar[List[str]] = ["occupied_by", "occupied_status", "progress", "state", "success", "wxid"]
|
|
37
39
|
|
|
38
40
|
model_config = ConfigDict(
|
|
39
41
|
validate_by_name=True,
|
|
@@ -74,6 +76,9 @@ class AccountConflict(BaseModel):
|
|
|
74
76
|
exclude=excluded_fields,
|
|
75
77
|
exclude_none=True,
|
|
76
78
|
)
|
|
79
|
+
# override the default output from pydantic by calling `to_dict()` of progress
|
|
80
|
+
if self.progress:
|
|
81
|
+
_dict['progress'] = self.progress.to_dict()
|
|
77
82
|
return _dict
|
|
78
83
|
|
|
79
84
|
@classmethod
|
|
@@ -88,6 +93,7 @@ class AccountConflict(BaseModel):
|
|
|
88
93
|
_obj = cls.model_validate({
|
|
89
94
|
"occupied_by": obj.get("occupied_by"),
|
|
90
95
|
"occupied_status": obj.get("occupied_status"),
|
|
96
|
+
"progress": IndexProgressDto.from_dict(obj["progress"]) if obj.get("progress") is not None else None,
|
|
91
97
|
"state": obj.get("state"),
|
|
92
98
|
"success": obj.get("success"),
|
|
93
99
|
"wxid": obj.get("wxid")
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.9.
|
|
8
|
+
The version of the OpenAPI document: 0.9.2
|
|
9
9
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
10
|
|
|
11
11
|
Do not edit the class manually.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.9.
|
|
8
|
+
The version of the OpenAPI document: 0.9.2
|
|
9
9
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
10
|
|
|
11
11
|
Do not edit the class manually.
|
{weflow_sdk-0.9.0 → weflow_sdk-0.9.2}/src/weflow_sdk/generated/weflow_sdk/models/account_phase.py
RENAMED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.9.
|
|
8
|
+
The version of the OpenAPI document: 0.9.2
|
|
9
9
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
10
|
|
|
11
11
|
Do not edit the class manually.
|