nc-user-terminator 0.1.3__tar.gz → 0.1.4__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.
Potentially problematic release.
This version of nc-user-terminator might be problematic. Click here for more details.
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/PKG-INFO +2 -1
- nc_user_terminator-0.1.4/nc_user_manager/cache.py +88 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_manager/client.py +82 -2
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/PKG-INFO +2 -1
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/SOURCES.txt +2 -0
- nc_user_terminator-0.1.4/nc_user_terminator.egg-info/requires.txt +1 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/pyproject.toml +4 -2
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/README.md +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_manager/__init__.py +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_manager/exceptions.py +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_manager/models.py +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_manager/utils.py +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/dependency_links.txt +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/top_level.txt +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/setup.cfg +0 -0
- {nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/setup.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nc-user-terminator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: OAuth client wrapper for multi-tenant projects
|
|
5
5
|
Author: bw_song
|
|
6
6
|
Author-email: m132777096902@gmail.com
|
|
7
7
|
Requires-Python: >=3.8
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cachetools==6.2.0
|
|
9
10
|
Dynamic: author-email
|
|
10
11
|
Dynamic: description-content-type
|
|
11
12
|
Dynamic: requires-python
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
from cachetools import TTLCache
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEFAULT_TTL = 3600
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseCache:
|
|
10
|
+
"""缓存接口"""
|
|
11
|
+
is_async = False
|
|
12
|
+
|
|
13
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def get(self, key: str) -> Any:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
def delete(self, key: str):
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------
|
|
24
|
+
# 内存缓存
|
|
25
|
+
# ---------------------
|
|
26
|
+
class MemoryCache(BaseCache):
|
|
27
|
+
is_async = False
|
|
28
|
+
|
|
29
|
+
def __init__(self, maxsize: int = 1000, ttl: int = DEFAULT_TTL):
|
|
30
|
+
self._cache = TTLCache(maxsize=maxsize, ttl=ttl)
|
|
31
|
+
|
|
32
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
33
|
+
# cachetools TTLCache 不支持 per-key TTL, 所以只使用全局 TTL
|
|
34
|
+
self._cache[key] = value
|
|
35
|
+
|
|
36
|
+
def get(self, key: str) -> Any:
|
|
37
|
+
return self._cache.get(key)
|
|
38
|
+
|
|
39
|
+
def delete(self, key: str):
|
|
40
|
+
self._cache.pop(key, None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------
|
|
44
|
+
# Redis 缓存
|
|
45
|
+
# ---------------------
|
|
46
|
+
class RedisCache(BaseCache):
|
|
47
|
+
"""同步 Redis"""
|
|
48
|
+
is_async = False
|
|
49
|
+
|
|
50
|
+
def __init__(self, redis_client, ttl: int = DEFAULT_TTL):
|
|
51
|
+
self.redis = redis_client
|
|
52
|
+
self.ttl = ttl
|
|
53
|
+
|
|
54
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
55
|
+
ttl = ttl or self.ttl
|
|
56
|
+
self.redis.setex(key, ttl, json.dumps(value))
|
|
57
|
+
|
|
58
|
+
def get(self, key: str) -> Any:
|
|
59
|
+
data = self.redis.get(key)
|
|
60
|
+
if data:
|
|
61
|
+
return json.loads(data)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def delete(self, key: str):
|
|
65
|
+
self.redis.delete(key)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AsyncRedisCache(BaseCache):
|
|
69
|
+
"""异步 Redis"""
|
|
70
|
+
is_async = True
|
|
71
|
+
|
|
72
|
+
def __init__(self, redis_client, ttl: int = DEFAULT_TTL):
|
|
73
|
+
self.redis = redis_client
|
|
74
|
+
self.ttl = ttl
|
|
75
|
+
|
|
76
|
+
async def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
77
|
+
ttl = ttl or self.ttl
|
|
78
|
+
await self.redis.setex(key, ttl, json.dumps(value))
|
|
79
|
+
|
|
80
|
+
async def get(self, key: str) -> Any:
|
|
81
|
+
data = await self.redis.get(key)
|
|
82
|
+
if data:
|
|
83
|
+
return json.loads(data)
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
async def delete(self, key: str):
|
|
87
|
+
await self.redis.delete(key)
|
|
88
|
+
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional, Dict, Any
|
|
3
3
|
|
|
4
|
+
from cache import MemoryCache, BaseCache, AsyncRedisCache
|
|
4
5
|
from models import UserResponse, OAuth2AuthorizeResponse, CallbackResponse
|
|
5
6
|
from exceptions import OAuthError
|
|
6
7
|
from utils import request
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
TENANT_HEADER = "X-Tenant-Code"
|
|
11
|
+
DEFAULT_TTL = 3600
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class OAuthClient:
|
|
@@ -16,6 +18,7 @@ class OAuthClient:
|
|
|
16
18
|
tenant_code: str,
|
|
17
19
|
redirect_url: Optional[str] = None,
|
|
18
20
|
single_session: bool = False,
|
|
21
|
+
cache: Optional[BaseCache] = None,
|
|
19
22
|
):
|
|
20
23
|
"""
|
|
21
24
|
OAuth 客户端
|
|
@@ -29,6 +32,9 @@ class OAuthClient:
|
|
|
29
32
|
self._tenant_code = tenant_code
|
|
30
33
|
self._redirect_url = redirect_url
|
|
31
34
|
self._single_session = single_session
|
|
35
|
+
self._cache = cache or MemoryCache(maxsize=10000, ttl=DEFAULT_TTL)
|
|
36
|
+
# 异步缓存,仅允许异步 redis
|
|
37
|
+
self._async_cache = self._cache.is_async
|
|
32
38
|
|
|
33
39
|
# ----------------------
|
|
34
40
|
# 内部异步包装
|
|
@@ -143,19 +149,83 @@ class OAuthClient:
|
|
|
143
149
|
)
|
|
144
150
|
return CallbackResponse(res_dict)
|
|
145
151
|
|
|
152
|
+
# ----------------------
|
|
153
|
+
# 缓存工具
|
|
154
|
+
# ----------------------
|
|
155
|
+
def _cache_user(self, token: str, user_dict: dict, ttl=DEFAULT_TTL):
|
|
156
|
+
key = f"user:{token}"
|
|
157
|
+
self._cache.set(key, user_dict, ttl)
|
|
158
|
+
|
|
159
|
+
if self._single_session:
|
|
160
|
+
user_id = user_dict.get("id")
|
|
161
|
+
if user_id:
|
|
162
|
+
old_token = self._cache.get(f"user_token:{user_id}")
|
|
163
|
+
if old_token and old_token != token:
|
|
164
|
+
self._cache.delete(f"user:{old_token}")
|
|
165
|
+
self._cache.set(f"user_token:{user_id}", token, ttl)
|
|
166
|
+
|
|
167
|
+
async def _cache_user_async(self, token: str, user_dict: dict, ttl=DEFAULT_TTL):
|
|
168
|
+
if not self._async_cache:
|
|
169
|
+
raise RuntimeError("异步缓存未配置")
|
|
170
|
+
|
|
171
|
+
key = f"user:{token}"
|
|
172
|
+
await self._cache.set(key, user_dict, ttl)
|
|
173
|
+
|
|
174
|
+
if self._single_session:
|
|
175
|
+
user_id = user_dict.get("id")
|
|
176
|
+
if user_id:
|
|
177
|
+
old_token = await self._cache.get(f"user_token:{user_id}")
|
|
178
|
+
if old_token and old_token != token:
|
|
179
|
+
await self._cache.delete(f"user:{old_token}")
|
|
180
|
+
await self._cache.set(f"user_token:{user_id}", token, ttl)
|
|
181
|
+
|
|
182
|
+
def _uncache_user(self, token: str, user_dict: Optional[dict] = None):
|
|
183
|
+
self._cache.delete(f"user:{token}")
|
|
184
|
+
if self._single_session and user_dict:
|
|
185
|
+
user_id = user_dict.get("id")
|
|
186
|
+
if user_id:
|
|
187
|
+
self._cache.delete(f"user_token:{user_id}")
|
|
188
|
+
|
|
189
|
+
async def _uncache_user_async(self, token: str, user_dict: Optional[dict] = None):
|
|
190
|
+
await self._cache.delete(f"user:{token}")
|
|
191
|
+
if self._single_session and user_dict:
|
|
192
|
+
user_id = user_dict.get("id")
|
|
193
|
+
if user_id:
|
|
194
|
+
await self._cache.delete(f"user_token:{user_id}")
|
|
195
|
+
|
|
146
196
|
# ----------------------
|
|
147
197
|
# 获取用户信息
|
|
148
198
|
# ----------------------
|
|
149
199
|
def say_my_name(self, token: str) -> UserResponse:
|
|
150
200
|
"""同步获取当前用户"""
|
|
201
|
+
if self._async_cache:
|
|
202
|
+
raise RuntimeError("同步缓存未配置")
|
|
203
|
+
|
|
204
|
+
key = f"user:{token}"
|
|
205
|
+
data = self._cache.get(key)
|
|
206
|
+
if data:
|
|
207
|
+
return UserResponse(data)
|
|
208
|
+
|
|
151
209
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
152
210
|
res_dict = request("GET", f"{self._base_url}/api/me", headers=headers)
|
|
211
|
+
|
|
212
|
+
self._cache_user(token, res_dict)
|
|
153
213
|
return UserResponse(res_dict)
|
|
154
214
|
|
|
155
215
|
async def say_my_name_async(self, token: str) -> UserResponse:
|
|
216
|
+
if not self._async_cache:
|
|
217
|
+
raise RuntimeError("异步方法只支持异步缓存,请传入 async_cache")
|
|
218
|
+
|
|
156
219
|
"""异步获取当前用户"""
|
|
220
|
+
key = f"user:{token}"
|
|
221
|
+
data = await self._cache.get(key)
|
|
222
|
+
if data:
|
|
223
|
+
return UserResponse(data)
|
|
224
|
+
|
|
157
225
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
158
226
|
res_dict = await self._arequest("GET", f"{self._base_url}/api/me", headers=headers)
|
|
227
|
+
|
|
228
|
+
await self._cache_user_async(token, res_dict)
|
|
159
229
|
return UserResponse(res_dict)
|
|
160
230
|
|
|
161
231
|
# 刷新过期时间
|
|
@@ -178,9 +248,19 @@ class OAuthClient:
|
|
|
178
248
|
# 登出
|
|
179
249
|
def logout(self, token: str):
|
|
180
250
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
181
|
-
|
|
251
|
+
resp = request("POST", f"{self._base_url}/api/auth/logout", headers=headers)
|
|
252
|
+
|
|
253
|
+
data = self._cache.get(f"user:{token}")
|
|
254
|
+
self._uncache_user(token, data)
|
|
255
|
+
return resp
|
|
256
|
+
|
|
182
257
|
|
|
183
258
|
async def logout_async(self, token: str) -> dict:
|
|
184
259
|
"""异步获取当前用户"""
|
|
185
260
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
186
|
-
|
|
261
|
+
resp = await self._arequest("POST", f"{self._base_url}/api/auth/logout", headers=headers)
|
|
262
|
+
|
|
263
|
+
data = await self._cache.get(f"user:{token}")
|
|
264
|
+
await self._uncache_user_async(token, data)
|
|
265
|
+
return resp
|
|
266
|
+
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nc-user-terminator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: OAuth client wrapper for multi-tenant projects
|
|
5
5
|
Author: bw_song
|
|
6
6
|
Author-email: m132777096902@gmail.com
|
|
7
7
|
Requires-Python: >=3.8
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cachetools==6.2.0
|
|
9
10
|
Dynamic: author-email
|
|
10
11
|
Dynamic: description-content-type
|
|
11
12
|
Dynamic: requires-python
|
{nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/SOURCES.txt
RENAMED
|
@@ -2,6 +2,7 @@ README.md
|
|
|
2
2
|
pyproject.toml
|
|
3
3
|
setup.py
|
|
4
4
|
nc_user_manager/__init__.py
|
|
5
|
+
nc_user_manager/cache.py
|
|
5
6
|
nc_user_manager/client.py
|
|
6
7
|
nc_user_manager/exceptions.py
|
|
7
8
|
nc_user_manager/models.py
|
|
@@ -9,4 +10,5 @@ nc_user_manager/utils.py
|
|
|
9
10
|
nc_user_terminator.egg-info/PKG-INFO
|
|
10
11
|
nc_user_terminator.egg-info/SOURCES.txt
|
|
11
12
|
nc_user_terminator.egg-info/dependency_links.txt
|
|
13
|
+
nc_user_terminator.egg-info/requires.txt
|
|
12
14
|
nc_user_terminator.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cachetools==6.2.0
|
|
@@ -4,10 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nc-user-terminator"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "OAuth client wrapper for multi-tenant projects"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "bw_song" }
|
|
11
11
|
]
|
|
12
12
|
requires-python = ">=3.8"
|
|
13
|
-
dependencies = [
|
|
13
|
+
dependencies = [
|
|
14
|
+
"cachetools==6.2.0"
|
|
15
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nc_user_terminator-0.1.3 → nc_user_terminator-0.1.4}/nc_user_terminator.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|