sa-token-python-core 0.1.4__tar.gz → 0.1.5__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.
Files changed (66) hide show
  1. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/PKG-INFO +3 -4
  2. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/README.md +2 -3
  3. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/examples/native/quick_start.py +1 -1
  4. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/pyproject.toml +1 -1
  5. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/config.py +1 -7
  6. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/django.py +0 -2
  7. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/flask.py +0 -4
  8. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/starlette.py +0 -2
  9. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/manager.py +22 -6
  10. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/stp_util.py +0 -3
  11. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/conftest.py +0 -9
  12. sa_token_python_core-0.1.4/src/sa_token/banner.py +0 -76
  13. sa_token_python_core-0.1.4/tests/test_banner.py +0 -106
  14. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/.github/workflows/ci.yml +0 -0
  15. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/.gitignore +0 -0
  16. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/LICENSE +0 -0
  17. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/examples/fastapi/main.py +0 -0
  18. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/examples/flask/main.py +0 -0
  19. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/__init__.py +0 -0
  20. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/adapter/__init__.py +0 -0
  21. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/adapter/http.py +0 -0
  22. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/adapter/path.py +0 -0
  23. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/adapter/pipeline.py +0 -0
  24. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/context.py +0 -0
  25. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/exception.py +0 -0
  26. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/__init__.py +0 -0
  27. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/fastapi.py +0 -0
  28. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/integration/fastapi_oauth2.py +0 -0
  29. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/listener.py +0 -0
  30. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/model.py +0 -0
  31. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/oauth2/__init__.py +0 -0
  32. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/oauth2/model.py +0 -0
  33. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/oauth2/server.py +0 -0
  34. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/online/__init__.py +0 -0
  35. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/permission.py +0 -0
  36. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/py.typed +0 -0
  37. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/security/__init__.py +0 -0
  38. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/security/nonce.py +0 -0
  39. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/security/refresh.py +0 -0
  40. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/security/temp_token.py +0 -0
  41. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/session.py +0 -0
  42. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/sso/__init__.py +0 -0
  43. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/storage/__init__.py +0 -0
  44. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/storage/base.py +0 -0
  45. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/storage/memory.py +0 -0
  46. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/storage/redis.py +0 -0
  47. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/stp_interface.py +0 -0
  48. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/stp_logic.py +0 -0
  49. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/strategy/__init__.py +0 -0
  50. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/strategy/base.py +0 -0
  51. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/strategy/builtin.py +0 -0
  52. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/strategy/jwt.py +0 -0
  53. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/sync.py +0 -0
  54. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/src/sa_token/token_io.py +0 -0
  55. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_adapter.py +0 -0
  56. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_auth.py +0 -0
  57. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_authorization.py +0 -0
  58. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_core_isolation.py +0 -0
  59. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_extensions.py +0 -0
  60. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_integration_fastapi.py +0 -0
  61. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_integration_fastapi_oauth2.py +0 -0
  62. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_integration_flask.py +0 -0
  63. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_optional_backends.py +0 -0
  64. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_permission.py +0 -0
  65. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_security.py +0 -0
  66. {sa_token_python_core-0.1.4 → sa_token_python_core-0.1.5}/tests/test_storage_and_token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sa-token-python-core
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: 有状态 Token 认证与权限鉴权框架(轻量核心)。灵感来源于 Java 的 Sa-Token。常用依赖请安装 sa-token-python;Web 框架按 extras 安装。
5
5
  Project-URL: Homepage, https://github.com/Star-yb/sa-token-python
6
6
  Project-URL: Documentation, https://github.com/Star-yb/sa-token-python
@@ -313,7 +313,6 @@ SaToken.builder() \
313
313
  | `is_write_cookie` | `False` | FastAPI 显式调用写 Cookie 助手时是否生效 |
314
314
  | `storage_key_prefix` | `satoken:` | 所有存储键前缀 |
315
315
  | `jwt_secret_key` | `None` | JWT 风格时必填 |
316
- | `is_print_banner` | `True` | 启动时打印 Sa-Token 标志,`.print_banner(False)` 关闭 |
317
316
 
318
317
  Token 读取顺序全局唯一:**Header → Authorization 兜底 → Cookie → Query**。各框架禁止自己再解析一遍。
319
318
 
@@ -505,8 +504,8 @@ class MyappConfig(AppConfig):
505
504
  from sa_token import SaToken
506
505
  from sa_token.storage import MemoryStorage, RedisStorage
507
506
 
508
- SaToken.builder().storage(MemoryStorage()).timeout(86400).build()
509
- SaToken.builder().storage(RedisStorage.from_url("redis://127.0.0.1:6379/0")).timeout(86400).build()
507
+ SaToken.builder().storage(MemoryStorage()).timeout(86400).print_banner(False).build()
508
+ SaToken.builder().storage(RedisStorage.from_url("redis://127.0.0.1:6379/0")).timeout(86400).print_banner(False).build()
510
509
  ```
511
510
 
512
511
  ### 2. 注册中间件
@@ -263,7 +263,6 @@ SaToken.builder() \
263
263
  | `is_write_cookie` | `False` | FastAPI 显式调用写 Cookie 助手时是否生效 |
264
264
  | `storage_key_prefix` | `satoken:` | 所有存储键前缀 |
265
265
  | `jwt_secret_key` | `None` | JWT 风格时必填 |
266
- | `is_print_banner` | `True` | 启动时打印 Sa-Token 标志,`.print_banner(False)` 关闭 |
267
266
 
268
267
  Token 读取顺序全局唯一:**Header → Authorization 兜底 → Cookie → Query**。各框架禁止自己再解析一遍。
269
268
 
@@ -455,8 +454,8 @@ class MyappConfig(AppConfig):
455
454
  from sa_token import SaToken
456
455
  from sa_token.storage import MemoryStorage, RedisStorage
457
456
 
458
- SaToken.builder().storage(MemoryStorage()).timeout(86400).build()
459
- SaToken.builder().storage(RedisStorage.from_url("redis://127.0.0.1:6379/0")).timeout(86400).build()
457
+ SaToken.builder().storage(MemoryStorage()).timeout(86400).print_banner(False).build()
458
+ SaToken.builder().storage(RedisStorage.from_url("redis://127.0.0.1:6379/0")).timeout(86400).print_banner(False).build()
460
459
  ```
461
460
 
462
461
  ### 2. 注册中间件
@@ -24,7 +24,7 @@ def on_auth_event(data: EventData) -> None:
24
24
 
25
25
 
26
26
  async def main() -> None:
27
- SaToken.builder().storage(MemoryStorage()).timeout(7200).on(
27
+ SaToken.builder().storage(MemoryStorage()).timeout(7200).print_banner(False).on(
28
28
  Event.ALL, on_auth_event
29
29
  ).build()
30
30
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sa-token-python-core"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "有状态 Token 认证与权限鉴权框架(轻量核心)。灵感来源于 Java 的 Sa-Token。常用依赖请安装 sa-token-python;Web 框架按 extras 安装。"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -105,13 +105,7 @@ class SaTokenConfig:
105
105
  jwt_algorithm: str = "HS256"
106
106
 
107
107
  is_print_banner: bool = True
108
- """是否打印启动横幅。默认 True,与 Java / Go 版一致。
109
-
110
- 原生 ``build()``、FastAPI / Starlette 服务启动、Flask ``init_app``、
111
- Django 中间件加载时都会尝试打印,同一进程只输出一次。
112
- 关闭:``SaToken.builder().print_banner(False)`` 或
113
- ``set_option(is_print_banner=False)``。
114
- """
108
+ """初始化时是否打印启动横幅。"""
115
109
 
116
110
  extra: dict[str, Any] = field(default_factory=dict)
117
111
  """给扩展模块(OAuth2 / SSO / 在线用户)放自定义配置。"""
@@ -14,7 +14,6 @@ from typing import Any, TypeVar
14
14
  from ..adapter.http import HttpContext
15
15
  from ..adapter.path import PathAuthConfig
16
16
  from ..adapter.pipeline import build_rule, resolve_token, run_auth_flow, run_path_auth
17
- from ..banner import emit_configured_banner
18
17
  from ..exception import SaTokenException
19
18
  from ..permission import MatchMode
20
19
  from ..stp_util import get_manager
@@ -79,7 +78,6 @@ class SaTokenDjangoMiddleware:
79
78
 
80
79
  def __init__(self, get_response: Callable[[Any], Any]) -> None:
81
80
  self._get_response = get_response
82
- emit_configured_banner()
83
81
 
84
82
  def __call__(self, request: Any) -> Any:
85
83
  ctx = DjangoHttpContext(request)
@@ -13,7 +13,6 @@ from typing import TYPE_CHECKING, Any, TypeVar
13
13
  from ..adapter.http import HttpContext
14
14
  from ..adapter.path import PathAuthConfig
15
15
  from ..adapter.pipeline import build_rule, resolve_token, run_auth_flow, run_path_auth
16
- from ..banner import emit_configured_banner
17
16
  from ..exception import SaTokenException
18
17
  from ..permission import MatchMode
19
18
  from ..stp_util import get_manager
@@ -78,9 +77,6 @@ class SaTokenFlask:
78
77
  def init_app(self, app: Flask) -> None:
79
78
  app.before_request(self._before_request)
80
79
  app.register_error_handler(SaTokenException, self._handle_exception)
81
- if hasattr(app, "before_serving"):
82
- app.before_serving(emit_configured_banner)
83
- emit_configured_banner()
84
80
 
85
81
  # 请求钩子 --------------------------------------------------------------
86
82
 
@@ -26,7 +26,6 @@ except ImportError as exc: # pragma: no cover - 依赖缺失路径
26
26
  from ..adapter.http import HttpContext
27
27
  from ..adapter.path import PathAuthConfig
28
28
  from ..adapter.pipeline import build_rule, resolve_token, run_auth_flow, run_path_auth
29
- from ..banner import emit_configured_banner
30
29
  from ..exception import SaTokenException
31
30
  from ..permission import MatchMode
32
31
  from ..stp_util import get_manager
@@ -100,7 +99,6 @@ class SaTokenMiddleware(BaseHTTPMiddleware):
100
99
  super().__init__(app)
101
100
  self.path_auth = path_auth
102
101
  self.login_type = login_type
103
- emit_configured_banner()
104
102
 
105
103
  async def dispatch(
106
104
  self,
@@ -6,9 +6,9 @@ Manager 负责组装:配置 + 存储 + Token 策略 + 事件总线 + 权限数
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import sys
9
10
  from typing import TYPE_CHECKING, Any
10
11
 
11
- from .banner import emit_banner, write_banner
12
12
  from .config import SaTokenConfig
13
13
  from .listener import Event, EventBus, Listener
14
14
  from .model import DEFAULT_LOGIN_TYPE
@@ -22,7 +22,15 @@ if TYPE_CHECKING: # pragma: no cover - 仅供类型检查
22
22
 
23
23
  __all__ = ["SaTokenManager", "SaTokenBuilder", "SaToken", "__version__"]
24
24
 
25
- __version__ = "0.1.4"
25
+ __version__ = "0.1.5"
26
+
27
+ _BANNER = r"""
28
+ _____ ______ __
29
+ / ___/____ _ /_ __/___ / /_____ ____
30
+ \__ \/ __ `/ / / / __ \/ //_/ _ \/ __ \
31
+ ___/ / /_/ / / / / /_/ / ,< / __/ / / /
32
+ /____/\__,_/ /_/ \____/_/|_|\___/_/ /_/
33
+ """
26
34
 
27
35
 
28
36
  class SaTokenManager:
@@ -46,7 +54,6 @@ class SaTokenManager:
46
54
  self._nonce_manager: NonceManager | None = None
47
55
  self._refresh_token_manager: RefreshTokenManager | None = None
48
56
  self._temp_token_manager: TempTokenManager | None = None
49
- emit_banner(self)
50
57
 
51
58
  def stp(self, login_type: str = DEFAULT_LOGIN_TYPE) -> StpLogic:
52
59
  """取得指定账号体系的逻辑实例,不同 ``login_type`` 之间存储完全隔离。"""
@@ -93,8 +100,16 @@ class SaTokenManager:
93
100
  return self._temp_token_manager
94
101
 
95
102
  def print_banner(self) -> None:
96
- """手动打印启动横幅,不受「只打印一次」限制。"""
97
- write_banner(self)
103
+ lines = [
104
+ _BANNER,
105
+ f":: sa-token-python :: (v{__version__})",
106
+ f":: Python :: {sys.version.split()[0]}",
107
+ f":: Token Style :: {self.config.token_style}",
108
+ f":: Token Timeout :: {self.config.timeout} 秒",
109
+ f":: Storage :: {type(self.storage).__name__}",
110
+ "",
111
+ ]
112
+ print("\n".join(lines))
98
113
 
99
114
 
100
115
  class SaTokenBuilder:
@@ -175,7 +190,6 @@ class SaTokenBuilder:
175
190
  return self
176
191
 
177
192
  def print_banner(self, value: bool) -> SaTokenBuilder:
178
- """是否打印启动横幅,默认 True。传入 False 关闭。"""
179
193
  self._config.is_print_banner = value
180
194
  return self
181
195
 
@@ -213,6 +227,8 @@ class SaTokenBuilder:
213
227
  stp_interface=self._stp_interface,
214
228
  events=self._events,
215
229
  )
230
+ if self._config.is_print_banner:
231
+ manager.print_banner()
216
232
  if self._set_global:
217
233
  from .stp_util import set_manager
218
234
 
@@ -29,9 +29,6 @@ def set_manager(manager: SaTokenManager) -> None:
29
29
  """设置全局 Manager,一般由 ``SaTokenBuilder.build()`` 自动调用。"""
30
30
  global _manager
31
31
  _manager = manager
32
- from .banner import emit_banner
33
-
34
- emit_banner(manager)
35
32
 
36
33
 
37
34
  def get_manager() -> SaTokenManager:
@@ -3,18 +3,10 @@ from __future__ import annotations
3
3
  import pytest
4
4
 
5
5
  from sa_token import SaToken, SaTokenManager
6
- from sa_token.banner import reset_banner_state
7
6
  from sa_token.storage import MemoryStorage
8
7
  from sa_token.stp_util import clear_manager
9
8
 
10
9
 
11
- @pytest.fixture(autouse=True)
12
- def _isolate_banner():
13
- reset_banner_state()
14
- yield
15
- reset_banner_state()
16
-
17
-
18
10
  @pytest.fixture
19
11
  def build_manager():
20
12
  """返回一个构建 Manager 的工厂,测试之间互不干扰。"""
@@ -30,7 +22,6 @@ def build_manager():
30
22
 
31
23
  yield _build
32
24
  clear_manager()
33
- reset_banner_state()
34
25
 
35
26
 
36
27
  @pytest.fixture
@@ -1,76 +0,0 @@
1
- """启动横幅。
2
-
3
- 与 Java / Go 版一致:初始化时默认打印一次 ASCII 标志。
4
- Web 框架适配会在服务启动时再尝试打印;进程内只输出一次。
5
- ``is_print_banner=False`` 或 ``.print_banner(False)`` 可关闭。
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import sys
11
- from typing import TYPE_CHECKING
12
-
13
- if TYPE_CHECKING: # pragma: no cover - 仅供类型检查
14
- from .manager import SaTokenManager
15
-
16
- __all__ = ["BANNER", "emit_banner", "emit_configured_banner", "reset_banner_state"]
17
-
18
- BANNER = r"""
19
- _____ ______ __
20
- / ___/____ _ /_ __/___ / /_____ ____
21
- \__ \/ __ `/ / / / __ \/ //_/ _ \/ __ \
22
- ___/ / /_/ / / / / /_/ / ,< / __/ / / /
23
- /____/\__,_/ /_/ \____/_/|_|\___/_/ /_/
24
- """
25
-
26
- _printed = False
27
-
28
-
29
- def reset_banner_state() -> None:
30
- """测试隔离:允许下一个用例再次打印。"""
31
- global _printed
32
- _printed = False
33
-
34
-
35
- def render_banner(manager: SaTokenManager) -> str:
36
- from .manager import __version__
37
-
38
- timeout = manager.config.timeout
39
- timeout_text = "永久" if timeout < 0 else f"{timeout} 秒"
40
- lines = [
41
- BANNER.rstrip("\n"),
42
- f":: sa-token-python :: (v{__version__})",
43
- f":: Python :: {sys.version.split()[0]}",
44
- f":: Token Style :: {manager.config.token_style}",
45
- f":: Token Timeout :: {timeout_text}",
46
- f":: Storage :: {type(manager.storage).__name__}",
47
- "",
48
- ]
49
- return "\n".join(lines)
50
-
51
-
52
- def write_banner(manager: SaTokenManager) -> None:
53
- """无条件写出横幅,供手动调用。"""
54
- sys.stdout.write(render_banner(manager) + "\n")
55
- sys.stdout.flush()
56
-
57
-
58
- def emit_banner(manager: SaTokenManager) -> None:
59
- """按配置打印横幅,同一进程只打印一次。"""
60
- global _printed
61
- if _printed or not manager.config.is_print_banner:
62
- return
63
- _printed = True
64
- write_banner(manager)
65
-
66
-
67
- def emit_configured_banner() -> None:
68
- """框架启动钩子:全局 Manager 已就绪则打印。"""
69
- from .exception import SaTokenNotInitializedException
70
- from .stp_util import get_manager
71
-
72
- try:
73
- manager = get_manager()
74
- except SaTokenNotInitializedException:
75
- return
76
- emit_banner(manager)
@@ -1,106 +0,0 @@
1
- """启动横幅:默认打印一次,可通过配置关闭。"""
2
-
3
- from __future__ import annotations
4
-
5
- import pytest
6
-
7
- from sa_token import SaToken, SaTokenConfig, SaTokenManager, __version__, set_manager
8
- from sa_token.banner import emit_configured_banner, reset_banner_state
9
- from sa_token.storage import MemoryStorage
10
- from sa_token.stp_util import clear_manager
11
-
12
-
13
- def test_build_prints_banner_by_default(capsys) -> None:
14
- SaToken.builder().storage(MemoryStorage()).as_global(False).build()
15
-
16
- output = capsys.readouterr().out
17
- assert "sa-token-python" in output
18
- assert f"(v{__version__})" in output
19
- assert "MemoryStorage" in output
20
- assert r"/ ___/____ _" in output
21
-
22
-
23
- def test_print_banner_false_silences_output(capsys) -> None:
24
- SaToken.builder().storage(MemoryStorage()).print_banner(False).as_global(False).build()
25
-
26
- assert capsys.readouterr().out == ""
27
-
28
-
29
- def test_set_option_can_disable_banner(capsys) -> None:
30
- SaToken.builder().storage(MemoryStorage()).set_option(is_print_banner=False).as_global(
31
- False
32
- ).build()
33
-
34
- assert capsys.readouterr().out == ""
35
-
36
-
37
- def test_banner_prints_only_once(capsys) -> None:
38
- manager = SaToken.builder().storage(MemoryStorage()).as_global(False).build()
39
- capsys.readouterr()
40
-
41
- manager.print_banner()
42
- assert "sa-token-python" in capsys.readouterr().out
43
-
44
- set_manager(manager)
45
- emit_configured_banner()
46
- assert capsys.readouterr().out == ""
47
-
48
-
49
- def test_direct_manager_construction_prints(capsys) -> None:
50
- SaTokenManager(SaTokenConfig(), MemoryStorage())
51
-
52
- output = capsys.readouterr().out
53
- assert "sa-token-python" in output
54
-
55
-
56
- def test_emit_configured_banner_without_manager_is_silent(capsys) -> None:
57
- clear_manager()
58
- emit_configured_banner()
59
- assert capsys.readouterr().out == ""
60
-
61
-
62
- def test_reset_allows_reprint(capsys) -> None:
63
- SaToken.builder().storage(MemoryStorage()).as_global(False).build()
64
- assert "sa-token-python" in capsys.readouterr().out
65
-
66
- reset_banner_state()
67
- SaToken.builder().storage(MemoryStorage()).as_global(False).build()
68
- assert "sa-token-python" in capsys.readouterr().out
69
-
70
-
71
- def test_fastapi_startup_prints_banner(capsys) -> None:
72
- pytest.importorskip("fastapi")
73
- from fastapi import FastAPI
74
- from fastapi.testclient import TestClient
75
-
76
- from sa_token.integration.fastapi import SaTokenMiddleware
77
-
78
- manager = SaToken.builder().storage(MemoryStorage()).print_banner(False).build()
79
- manager.config.is_print_banner = True
80
- reset_banner_state()
81
-
82
- app = FastAPI()
83
- app.add_middleware(SaTokenMiddleware)
84
- with TestClient(app):
85
- pass
86
-
87
- output = capsys.readouterr().out
88
- assert "sa-token-python" in output
89
- assert "MemoryStorage" in output
90
-
91
-
92
- def test_flask_init_prints_banner(capsys) -> None:
93
- pytest.importorskip("flask")
94
- from flask import Flask
95
-
96
- from sa_token.integration.flask import SaTokenFlask
97
-
98
- SaToken.builder().storage(MemoryStorage()).build()
99
- capsys.readouterr()
100
- reset_banner_state()
101
-
102
- app = Flask(__name__)
103
- SaTokenFlask(app)
104
-
105
- output = capsys.readouterr().out
106
- assert "sa-token-python" in output