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,11 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """py_wisharetec_toolkit 顶层包。
4
+
5
+ 可供他人通过 `import py_wisharetec_toolkit` 导入。
6
+ """
7
+
8
+ __version__ = "3.0.2"
9
+ __all__ = ["shanshu"]
10
+
11
+ from . import shanshu # noqa: E402
File without changes
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+
4
+ """shanshu 子包。
5
+
6
+ 可供他人通过 `from py_wisharetec_toolkit import shanshu` 或
7
+ `from py_wisharetec_toolkit.shanshu import client, models, auth` 导入。
8
+ """
9
+
10
+ __version__ = "3.0.2"
11
+ __all__ = ["client", "models", "auth"]
12
+
13
+ from . import client # noqa: E402
14
+ from . import models # noqa: E402
15
+ from . import auth # noqa: E402
16
+
17
+ # 将异步客户端类也导出到顶层,方便直接使用
18
+ from .client import AsyncShanshuClient, AsyncShanshuClientConfig # noqa: E402
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+
4
+ """Shanshu 登录认证与 Token 缓存。
5
+
6
+ 提供基于 httpx 的 ``/api/user/loginInteractive`` 接口调用,
7
+ 并支持 memory / diskcache / redis 三种 Token 缓存后端。
8
+
9
+ 缓存 key 格式:py_wisharetec_toolkit:shanshu:authorization_token:{account}
10
+ 便于缓存不同用户的登录 token。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import threading
17
+ from typing import Any, Protocol, runtime_checkable
18
+
19
+ LOGIN_PATH = "api/user/loginInteractive"
20
+ CACHE_KEY_PREFIX = "py_wisharetec_toolkit:shanshu:authorization_token"
21
+
22
+
23
+ def cache_key(account: str) -> str:
24
+ """构造 Token 缓存 key。
25
+
26
+ 格式:py_wisharetec_toolkit:shanshu:authorization_token:{account}
27
+ """
28
+ return f"{CACHE_KEY_PREFIX}:{account}"
29
+
30
+
31
+ @runtime_checkable
32
+ class ShanshuTokenCache(Protocol):
33
+ """Token 缓存协议,key 为 cache_key(account) 生成的字符串。"""
34
+
35
+ def get(self, key: str) -> str | None: ...
36
+
37
+ def set(self, key: str, token: str, ttl: int | None = None) -> None: ...
38
+
39
+
40
+ class ShanshuMemoryTokenCache:
41
+ """进程内内存缓存(默认后端,无需额外依赖,线程安全)。"""
42
+
43
+ def __init__(self) -> None:
44
+ self._tokens: dict[str, str] = {}
45
+ self._lock = threading.Lock()
46
+
47
+ def get(self, key: str) -> str | None:
48
+ with self._lock:
49
+ return self._tokens.get(key)
50
+
51
+ def set(self, key: str, token: str, ttl: int | None = None) -> None:
52
+ with self._lock:
53
+ self._tokens[key] = token
54
+
55
+
56
+ class ShanshuDiskCacheTokenCache:
57
+ """基于 diskcache 的持久化缓存。
58
+
59
+ 需要额外安装:pip install diskcache
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ cache_dir: str = ".runtime/cache/diskcache",
65
+ table: str = "token",
66
+ expire: int | None = None,
67
+ ) -> None:
68
+ try:
69
+ import diskcache as dc
70
+ except ImportError as exc: # pragma: no cover
71
+ raise ImportError(
72
+ "使用 ShanshuDiskCacheTokenCache 需要安装 diskcache: pip install diskcache"
73
+ ) from exc
74
+ self._cache = dc.Cache(cache_dir, table=table)
75
+ self._expire = expire
76
+
77
+ def get(self, key: str) -> str | None:
78
+ return self._cache.get(key)
79
+
80
+ def set(self, key: str, token: str, ttl: int | None = None) -> None:
81
+ expire = ttl if ttl is not None else self._expire
82
+ self._cache.set(key, token, expire=expire)
83
+
84
+
85
+ class ShanshuRedisTokenCache:
86
+ """基于 redis 的缓存。
87
+
88
+ 需要额外安装:pip install redis
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ host: str = "localhost",
94
+ port: int = 6379,
95
+ db: int = 0,
96
+ password: str | None = None,
97
+ expire: int | None = None,
98
+ **kwargs: Any,
99
+ ) -> None:
100
+ try:
101
+ import redis as redis_lib
102
+ except ImportError as exc: # pragma: no cover
103
+ raise ImportError(
104
+ "使用 ShanshuRedisTokenCache 需要安装 redis: pip install redis"
105
+ ) from exc
106
+ self._client = redis_lib.Redis(
107
+ host=host,
108
+ port=port,
109
+ db=db,
110
+ password=password,
111
+ **kwargs,
112
+ )
113
+ self._expire = expire
114
+
115
+ def get(self, key: str) -> str | None:
116
+ val = self._client.get(key)
117
+ if val is None:
118
+ return None
119
+ return val.decode() if isinstance(val, bytes) else val
120
+
121
+ def set(self, key: str, token: str, ttl: int | None = None) -> None:
122
+ expire = ttl if ttl is not None else self._expire
123
+ if expire is not None:
124
+ self._client.set(key, token, ex=expire)
125
+ else:
126
+ self._client.set(key, token)
127
+
128
+
129
+ def md5(text: str) -> str:
130
+ """计算字符串的 MD5 哈希(小写十六进制)。"""
131
+ return hashlib.md5(text.encode("utf-8")).hexdigest()
132
+
133
+
134
+ __all__ = [
135
+ "LOGIN_PATH",
136
+ "CACHE_KEY_PREFIX",
137
+ "cache_key",
138
+ "ShanshuTokenCache",
139
+ "ShanshuMemoryTokenCache",
140
+ "ShanshuDiskCacheTokenCache",
141
+ "ShanshuRedisTokenCache",
142
+ "md5",
143
+ ]