fastlib-py 0.2.0__tar.gz → 0.2.1__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.
- {fastlib_py-0.2.0/fastlib_py.egg-info → fastlib_py-0.2.1}/PKG-INFO +1 -1
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/cache/__init__.py +1 -1
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/cache/disk_cache.py +2 -2
- fastlib_py-0.2.0/fastlib/cache/cache_manager.py → fastlib_py-0.2.1/fastlib/cache/manager.py +4 -3
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/cache/redis_cache.py +6 -7
- fastlib_py-0.2.1/fastlib/config/base.py +17 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/utils.py +0 -2
- fastlib_py-0.2.1/fastlib/i18n/manager.py +96 -0
- fastlib_py-0.2.1/fastlib/i18n/messages.py +88 -0
- fastlib_py-0.2.1/fastlib/i18n/utils.py +86 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/logging/config.py +9 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/mapper/impl/base_mapper_impl.py +1 -1
- fastlib_py-0.2.1/fastlib/middleware/__init__.py +7 -0
- fastlib_py-0.2.1/fastlib/middleware/i18n.py +57 -0
- fastlib_py-0.2.0/fastlib/middleware/jwt_middleware.py → fastlib_py-0.2.1/fastlib/middleware/jwt.py +1 -1
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/model/base_model.py +2 -2
- {fastlib_py-0.2.0 → fastlib_py-0.2.1/fastlib_py.egg-info}/PKG-INFO +1 -1
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib_py.egg-info/SOURCES.txt +11 -10
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/pyproject.toml +1 -1
- fastlib_py-0.2.0/fastlib/config/base.py +0 -6
- fastlib_py-0.2.0/fastlib/i18n/i18n_context.py +0 -215
- fastlib_py-0.2.0/fastlib/i18n/i18n_messages.py +0 -99
- fastlib_py-0.2.0/fastlib/i18n/i18n_util.py +0 -233
- fastlib_py-0.2.0/fastlib/middleware/__init__.py +0 -7
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/LICENSE +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/README.md +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/__init__.py +0 -0
- /fastlib_py-0.2.0/fastlib/cache/base_cache.py → /fastlib_py-0.2.1/fastlib/cache/base.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/database_config.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/loader.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/manager.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/registry.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/security_config.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/config/server_config.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/constant/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/constant/constant.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/context/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/context/contextvars.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/enums/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/enums/base_error_code.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/enums/enum.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/exception/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/exception/custom_exception.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/exception/exception_handler.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/exception/exception_manager.py +0 -0
- /fastlib_py-0.2.0/fastlib/i18n/i18n_types.py → /fastlib_py-0.2.1/fastlib/i18n/types.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/logging/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/logging/handlers.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/mapper/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/mapper/base_mapper.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/mapper/impl/__init__.py +0 -0
- /fastlib_py-0.2.0/fastlib/middleware/db_session_middleware.py → /fastlib_py-0.2.1/fastlib/middleware/db_session.py +0 -0
- /fastlib_py-0.2.0/fastlib/middleware/log_middleware.py → /fastlib_py-0.2.1/fastlib/middleware/log.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/migrate/migrate.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/model/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/openapi/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/openapi/offline.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/router/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/router/router_v1.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/schema/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/schema/response.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/schema/schema.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/security/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/security/security.py +0 -0
- /fastlib_py-0.2.0/fastlib/utils/security_util.py → /fastlib_py-0.2.1/fastlib/security/utils.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/service/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/service/base_service.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/service/impl/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/service/impl/base_service_impl.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/session/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/session/db_engine.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/session/db_session.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/__init__.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/excel_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/file_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/http_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/ip_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/snowflake_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib/utils/str_util.py +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib_py.egg-info/dependency_links.txt +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib_py.egg-info/requires.txt +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/fastlib_py.egg-info/top_level.txt +0 -0
- {fastlib_py-0.2.0 → fastlib_py-0.2.1}/setup.cfg +0 -0
|
@@ -5,11 +5,11 @@ import json
|
|
|
5
5
|
import threading
|
|
6
6
|
import time
|
|
7
7
|
from contextlib import asynccontextmanager
|
|
8
|
-
from typing import Any, Optional
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
import diskcache
|
|
11
11
|
|
|
12
|
-
from fastlib.cache.
|
|
12
|
+
from fastlib.cache.base import Cache, CacheError
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class PageCache(Cache):
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
"""Cache Client manager to instantiate the appropriate cache client"""
|
|
3
3
|
|
|
4
|
-
from fastlib.cache.
|
|
5
|
-
|
|
4
|
+
from fastlib.cache.base import Cache
|
|
5
|
+
|
|
6
6
|
from fastlib.config.manager import ConfigManager
|
|
7
7
|
|
|
8
8
|
|
|
@@ -12,7 +12,8 @@ async def get_cache_client() -> Cache:
|
|
|
12
12
|
Returns:
|
|
13
13
|
Cache: Redis client if Redis is enabled in config, otherwise returns page cache.
|
|
14
14
|
"""
|
|
15
|
-
|
|
15
|
+
from fastlib.cache.redis_cache import RedisManager
|
|
16
|
+
|
|
16
17
|
config = ConfigManager.get_database_config()
|
|
17
18
|
if config.enable_redis:
|
|
18
19
|
from fastlib.cache.redis_cache import RedisCache
|
|
@@ -5,17 +5,18 @@ import asyncio
|
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
7
|
from contextlib import asynccontextmanager
|
|
8
|
-
from typing import Any, Optional
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
from loguru import logger
|
|
11
11
|
|
|
12
|
-
from fastlib
|
|
12
|
+
from fastlib import ConfigManager
|
|
13
|
+
from fastlib.cache.base import Cache, CacheError
|
|
13
14
|
|
|
14
15
|
try:
|
|
15
16
|
import redis.asyncio as redis
|
|
16
17
|
from redis.exceptions import RedisError
|
|
17
18
|
except ModuleNotFoundError:
|
|
18
|
-
logger.
|
|
19
|
+
logger.warning(
|
|
19
20
|
"Cannot find redis module, please install it via `uv add redis[hiredis] and then uv remove diskcache`"
|
|
20
21
|
)
|
|
21
22
|
redis = None
|
|
@@ -25,8 +26,6 @@ except Exception:
|
|
|
25
26
|
redis = None
|
|
26
27
|
RedisError = Exception
|
|
27
28
|
|
|
28
|
-
from fastlib.config.manager import load_config
|
|
29
|
-
|
|
30
29
|
|
|
31
30
|
class RedisCache(Cache):
|
|
32
31
|
def __init__(self, redis_client):
|
|
@@ -347,9 +346,9 @@ class RedisManager:
|
|
|
347
346
|
if cls._instance is None:
|
|
348
347
|
async with cls._lock:
|
|
349
348
|
if cls._connection_pool is None:
|
|
350
|
-
|
|
349
|
+
database_config = ConfigManager.get_database_config()
|
|
351
350
|
cls._connection_pool = redis.ConnectionPool.from_url(
|
|
352
|
-
f"redis://:{
|
|
351
|
+
f"redis://:{database_config.cache_pass}@{database_config.cache_host}:{database_config.cache_port}/{database_config.db_num}",
|
|
353
352
|
decode_responses=True,
|
|
354
353
|
)
|
|
355
354
|
if cls._instance is None:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Base configuration classes."""
|
|
3
|
+
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseConfig(ABC):
|
|
8
|
+
"""Base class for all configuration classes."""
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def __str__(self) -> str:
|
|
12
|
+
"""Return string representation of the configuration.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: String representation of the configuration instance.
|
|
16
|
+
"""
|
|
17
|
+
pass
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
|
-
|
|
3
2
|
"""Configuration module util"""
|
|
4
3
|
|
|
5
4
|
import configparser
|
|
@@ -60,7 +59,6 @@ class ProjectInfo:
|
|
|
60
59
|
path = Path(file_util.find_project_root()) / "pyproject.toml"
|
|
61
60
|
else:
|
|
62
61
|
path = Path(pyproject_path)
|
|
63
|
-
print(f"{path}")
|
|
64
62
|
if not path.exists():
|
|
65
63
|
raise FileNotFoundError(f"{pyproject_path} does not exist")
|
|
66
64
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internationalization context manager
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import contextvars
|
|
6
|
+
import functools
|
|
7
|
+
from typing import Any, Callable, TypeVar, Awaitable
|
|
8
|
+
from fastlib.i18n.types import Language
|
|
9
|
+
|
|
10
|
+
_language_context = contextvars.ContextVar("language", default=Language.ENGLISH)
|
|
11
|
+
|
|
12
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_language(language: Language) -> None:
|
|
16
|
+
"""Set current language"""
|
|
17
|
+
_language_context.set(language)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_language() -> Language:
|
|
21
|
+
"""Get current language"""
|
|
22
|
+
return _language_context.get()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_language_code() -> str:
|
|
26
|
+
"""Get current language code, e.g. 'en', 'zh'"""
|
|
27
|
+
return get_language().value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reset_language() -> None:
|
|
31
|
+
"""Reset language to default"""
|
|
32
|
+
_language_context.set(Language.ENGLISH)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_language(language: Language) -> bool:
|
|
36
|
+
"""Check if current language matches"""
|
|
37
|
+
return get_language() == language
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class I18nContext:
|
|
41
|
+
"""Context manager to temporarily switch language"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, language: Language):
|
|
44
|
+
self.language = language
|
|
45
|
+
self._token: contextvars.Token | None = None
|
|
46
|
+
|
|
47
|
+
def __enter__(self):
|
|
48
|
+
self._token = _language_context.set(self.language)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
52
|
+
if self._token is not None:
|
|
53
|
+
_language_context.reset(self._token)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def with_language(language: Language) -> I18nContext:
|
|
57
|
+
"""Helper to create context manager"""
|
|
58
|
+
return I18nContext(language)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _language_context_decorator(
|
|
62
|
+
language: Language, is_async: bool = False
|
|
63
|
+
) -> Callable[[F], F]:
|
|
64
|
+
"""Internal generator for sync/async language context decorators"""
|
|
65
|
+
|
|
66
|
+
def decorator(func: F) -> F:
|
|
67
|
+
if is_async:
|
|
68
|
+
|
|
69
|
+
@functools.wraps(func)
|
|
70
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
71
|
+
with I18nContext(language):
|
|
72
|
+
return await func(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
return async_wrapper
|
|
75
|
+
else:
|
|
76
|
+
|
|
77
|
+
@functools.wraps(func)
|
|
78
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
79
|
+
with I18nContext(language):
|
|
80
|
+
return func(*args, **kwargs)
|
|
81
|
+
|
|
82
|
+
return wrapper
|
|
83
|
+
|
|
84
|
+
return decorator
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def language_context(language: Language) -> Callable[[F], F]:
|
|
88
|
+
"""Decorator: run a sync function within a language context"""
|
|
89
|
+
return _language_context_decorator(language, is_async=False)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def async_language_context(
|
|
93
|
+
language: Language,
|
|
94
|
+
) -> Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]]:
|
|
95
|
+
"""Decorator: run an async function within a language context"""
|
|
96
|
+
return _language_context_decorator(language, is_async=True)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
from fastlib.constant.constant import RESOURCE_DIR
|
|
5
|
+
from fastlib.i18n.types import Language
|
|
6
|
+
|
|
7
|
+
# Default i18n directory (assumed to be in the same directory as this file)
|
|
8
|
+
DEFAULT_BASE_DIR = Path(RESOURCE_DIR) / "i18n"
|
|
9
|
+
|
|
10
|
+
# Global cache for translation data
|
|
11
|
+
TRANSLATIONS = {
|
|
12
|
+
"zh": {"debug": {"test_message": "这是一条调试测试消息"}},
|
|
13
|
+
"en": {"debug": {"test_message": "This is a debug test message"}},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_translations(base_dirs: Union[Path, List[Path], None] = None):
|
|
18
|
+
"""Load multilingual translation files from multiple directories.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
base_dirs: Single directory path or list of directory paths for translation files.
|
|
22
|
+
If not provided, uses the default DEFAULT_BASE_DIR.
|
|
23
|
+
|
|
24
|
+
File structure requirements:
|
|
25
|
+
Each directory should follow: base_dir/{lang}/{namespace}.json
|
|
26
|
+
Example: i18n/zh/error.json
|
|
27
|
+
|
|
28
|
+
Note: Later directories will override translations from earlier directories
|
|
29
|
+
if the same language and namespace exist.
|
|
30
|
+
"""
|
|
31
|
+
if base_dirs is None:
|
|
32
|
+
base_dirs = [DEFAULT_BASE_DIR]
|
|
33
|
+
elif isinstance(base_dirs, Path):
|
|
34
|
+
base_dirs = [base_dirs]
|
|
35
|
+
|
|
36
|
+
for base_dir in base_dirs:
|
|
37
|
+
if not base_dir.exists():
|
|
38
|
+
continue # Skip if directory doesn't exist
|
|
39
|
+
|
|
40
|
+
for lang in [Language.CHINESE, Language.ENGLISH]:
|
|
41
|
+
lang_dir = base_dir / lang.value
|
|
42
|
+
if not lang_dir.exists():
|
|
43
|
+
continue # Skip if language directory doesn't exist
|
|
44
|
+
|
|
45
|
+
# Initialize language dict if not exists
|
|
46
|
+
if lang.value not in TRANSLATIONS:
|
|
47
|
+
TRANSLATIONS[lang.value] = {}
|
|
48
|
+
|
|
49
|
+
for file in lang_dir.glob("*.json"):
|
|
50
|
+
namespace = (
|
|
51
|
+
file.stem
|
|
52
|
+
) # Determine message_type from filename, e.g., error, success
|
|
53
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
54
|
+
translations_data = json.load(f)
|
|
55
|
+
|
|
56
|
+
# Merge with existing namespace data or create new
|
|
57
|
+
if namespace in TRANSLATIONS[lang.value]:
|
|
58
|
+
TRANSLATIONS[lang.value][namespace].update(translations_data)
|
|
59
|
+
else:
|
|
60
|
+
TRANSLATIONS[lang.value][namespace] = translations_data
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_message(message_type: str, key: str, language: Language) -> str:
|
|
64
|
+
"""Get translated message for given type, key and language.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
message_type: Namespace/category of the message (e.g., 'error', 'success')
|
|
68
|
+
key: Message identifier key
|
|
69
|
+
language: Target language
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Translated message or fallback to default language if not found
|
|
73
|
+
"""
|
|
74
|
+
default_language = Language.CHINESE.value
|
|
75
|
+
|
|
76
|
+
# Try to get message from requested language
|
|
77
|
+
lang_dict = TRANSLATIONS.get(language.value, {})
|
|
78
|
+
msg = lang_dict.get(message_type, {}).get(key)
|
|
79
|
+
|
|
80
|
+
if msg:
|
|
81
|
+
return msg
|
|
82
|
+
|
|
83
|
+
# Fallback to default language
|
|
84
|
+
return (
|
|
85
|
+
TRANSLATIONS.get(default_language, {})
|
|
86
|
+
.get(message_type, {})
|
|
87
|
+
.get(key, f"Unknown {message_type} message: {key}")
|
|
88
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internationalization utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .manager import I18nContext
|
|
8
|
+
from .messages import get_message
|
|
9
|
+
from .types import Language
|
|
10
|
+
|
|
11
|
+
# Default language
|
|
12
|
+
DEFAULT_LANGUAGE = Language.ENGLISH
|
|
13
|
+
|
|
14
|
+
# Language alias mapping
|
|
15
|
+
LANGUAGE_MAPPING = {
|
|
16
|
+
"zh": Language.CHINESE,
|
|
17
|
+
"zh_cn": Language.CHINESE,
|
|
18
|
+
"en": Language.ENGLISH,
|
|
19
|
+
"en_us": Language.ENGLISH,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# --- Helpers ---
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_language(language: Optional[Language]) -> Language:
|
|
26
|
+
"""Return given language or current context default"""
|
|
27
|
+
return language or I18nContext.get_language()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# --- Core utilities ---
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_language(language_str: Optional[str]) -> Language:
|
|
34
|
+
"""Parse string like 'zh-CN', 'en-US' to Language enum"""
|
|
35
|
+
if not language_str:
|
|
36
|
+
return DEFAULT_LANGUAGE
|
|
37
|
+
return LANGUAGE_MAPPING.get(
|
|
38
|
+
language_str.lower().replace("-", "_"), DEFAULT_LANGUAGE
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_localized_message(
|
|
43
|
+
category: str, key: str, language: Optional[Language] = None
|
|
44
|
+
) -> str:
|
|
45
|
+
"""Get localized message by category ('error', 'success', 'business')"""
|
|
46
|
+
lang = _resolve_language(language)
|
|
47
|
+
return get_message(category, key, lang)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_error_message(key: str, language: Optional[Language] = None) -> str:
|
|
51
|
+
return get_localized_message("error", key, language)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_success_message(key: str, language: Optional[Language] = None) -> str:
|
|
55
|
+
return get_localized_message("success", key, language)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_error_response(
|
|
59
|
+
error_key: str,
|
|
60
|
+
language: Optional[Language] = None,
|
|
61
|
+
details: Optional[str] = None,
|
|
62
|
+
error_code: int = 400,
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
lang = _resolve_language(language)
|
|
65
|
+
error = {
|
|
66
|
+
"code": error_code,
|
|
67
|
+
"message": get_error_message(error_key, lang),
|
|
68
|
+
"language": lang.value,
|
|
69
|
+
}
|
|
70
|
+
if details:
|
|
71
|
+
error["details"] = details
|
|
72
|
+
return {"success": False, "error": error}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_success_response(
|
|
76
|
+
data: Any,
|
|
77
|
+
language: Optional[Language] = None,
|
|
78
|
+
message_key: str = "search_success",
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
lang = _resolve_language(language)
|
|
81
|
+
return {
|
|
82
|
+
"success": True,
|
|
83
|
+
"data": data,
|
|
84
|
+
"message": get_success_message(message_key, lang),
|
|
85
|
+
"language": lang.value,
|
|
86
|
+
}
|
|
@@ -64,3 +64,12 @@ class LogConfig(BaseConfig):
|
|
|
64
64
|
def error_log_file_pattern(self) -> str:
|
|
65
65
|
"""Returns the error log file pattern with timestamp."""
|
|
66
66
|
return f"{{time:{self.time_format}}}_error_{self.app_name}.log"
|
|
67
|
+
|
|
68
|
+
def __str__(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Returns a string representation of the database configuration.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A string representation of the DatabaseConfig instance.
|
|
74
|
+
"""
|
|
75
|
+
return f"{self.__class__.__name__}({self.__dict__})"
|
|
@@ -11,7 +11,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|
|
11
11
|
from fastlib.constant import FilterOperators, constant
|
|
12
12
|
from fastlib.enums import SortEnum
|
|
13
13
|
from fastlib.mapper.base_mapper import BaseMapper
|
|
14
|
-
from fastlib.middleware.
|
|
14
|
+
from fastlib.middleware.db_session import db
|
|
15
15
|
from fastlib.schema import SortItem
|
|
16
16
|
|
|
17
17
|
IDType = TypeVar("IDType", int, str)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
3
|
+
|
|
4
|
+
from fastlib.i18n.types import Language
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class I18nMiddleware(BaseHTTPMiddleware):
|
|
8
|
+
"""
|
|
9
|
+
Middleware for automatic language detection and setting based on HTTP headers.
|
|
10
|
+
|
|
11
|
+
This middleware inspects the Accept-Language header from incoming requests
|
|
12
|
+
and sets the appropriate language in the request state for use throughout
|
|
13
|
+
the application.
|
|
14
|
+
|
|
15
|
+
The language preference is stored in request.state.language and can be
|
|
16
|
+
accessed in route handlers and other middleware.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
```python
|
|
20
|
+
from fastapi import FastAPI, Request
|
|
21
|
+
|
|
22
|
+
app = FastAPI()
|
|
23
|
+
app.add_middleware(I18nMiddleware)
|
|
24
|
+
|
|
25
|
+
@app.get("/")
|
|
26
|
+
async def root(request: Request):
|
|
27
|
+
language = request.state.language
|
|
28
|
+
return {"language": language.value}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Note:
|
|
32
|
+
Currently supports only English and Chinese. Defaults to English
|
|
33
|
+
if the Accept-Language header is not present or unrecognized.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
async def dispatch(self, request, call_next):
|
|
37
|
+
"""
|
|
38
|
+
Process the request and set the language based on Accept-Language header.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
request: The incoming HTTP request
|
|
42
|
+
call_next: The next middleware or route handler in the chain
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The HTTP response from the next handler
|
|
46
|
+
"""
|
|
47
|
+
# Get language preference from request headers
|
|
48
|
+
accept_lang = request.headers.get("Accept-Language", "en")
|
|
49
|
+
|
|
50
|
+
# Set the language for current request
|
|
51
|
+
if accept_lang.startswith("en"):
|
|
52
|
+
request.state.language = Language.ENGLISH
|
|
53
|
+
else:
|
|
54
|
+
request.state.language = Language.CHINESE
|
|
55
|
+
|
|
56
|
+
response = await call_next(request)
|
|
57
|
+
return response
|
fastlib_py-0.2.0/fastlib/middleware/jwt_middleware.py → fastlib_py-0.2.1/fastlib/middleware/jwt.py
RENAMED
|
@@ -6,12 +6,12 @@ import http
|
|
|
6
6
|
from fastapi import Request
|
|
7
7
|
from jwt.exceptions import PyJWTError
|
|
8
8
|
from loguru import logger
|
|
9
|
-
from src.main.app.enums.auth_error_code import AuthErrorCode
|
|
10
9
|
from starlette.responses import JSONResponse
|
|
11
10
|
|
|
12
11
|
from fastlib import ConfigManager, constant, security
|
|
13
12
|
from fastlib.context.contextvars import current_user_id
|
|
14
13
|
from fastlib.enums.enum import MediaTypeEnum
|
|
14
|
+
from fastlib.enums import AuthErrorCode
|
|
15
15
|
from fastlib.schema import UserCredential
|
|
16
16
|
|
|
17
17
|
# Load configuration
|
|
@@ -47,12 +47,12 @@ class ModelExt(_SQLModel):
|
|
|
47
47
|
Create time and update time for a model, can be automatically generated
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
create_at: Optional[datetime] = Field(
|
|
51
51
|
sa_type=DateTime,
|
|
52
52
|
default_factory=datetime.utcnow,
|
|
53
53
|
sa_column_kwargs={"comment": "创建时间"},
|
|
54
54
|
)
|
|
55
|
-
|
|
55
|
+
update_at: Optional[datetime] = Field(
|
|
56
56
|
sa_type=DateTime,
|
|
57
57
|
default_factory=datetime.utcnow,
|
|
58
58
|
sa_column_kwargs={
|
|
@@ -3,9 +3,9 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
fastlib/__init__.py
|
|
5
5
|
fastlib/cache/__init__.py
|
|
6
|
-
fastlib/cache/
|
|
7
|
-
fastlib/cache/cache_manager.py
|
|
6
|
+
fastlib/cache/base.py
|
|
8
7
|
fastlib/cache/disk_cache.py
|
|
8
|
+
fastlib/cache/manager.py
|
|
9
9
|
fastlib/cache/redis_cache.py
|
|
10
10
|
fastlib/config/__init__.py
|
|
11
11
|
fastlib/config/base.py
|
|
@@ -27,10 +27,10 @@ fastlib/exception/__init__.py
|
|
|
27
27
|
fastlib/exception/custom_exception.py
|
|
28
28
|
fastlib/exception/exception_handler.py
|
|
29
29
|
fastlib/exception/exception_manager.py
|
|
30
|
-
fastlib/i18n/
|
|
31
|
-
fastlib/i18n/
|
|
32
|
-
fastlib/i18n/
|
|
33
|
-
fastlib/i18n/
|
|
30
|
+
fastlib/i18n/manager.py
|
|
31
|
+
fastlib/i18n/messages.py
|
|
32
|
+
fastlib/i18n/types.py
|
|
33
|
+
fastlib/i18n/utils.py
|
|
34
34
|
fastlib/logging/__init__.py
|
|
35
35
|
fastlib/logging/config.py
|
|
36
36
|
fastlib/logging/handlers.py
|
|
@@ -39,9 +39,10 @@ fastlib/mapper/base_mapper.py
|
|
|
39
39
|
fastlib/mapper/impl/__init__.py
|
|
40
40
|
fastlib/mapper/impl/base_mapper_impl.py
|
|
41
41
|
fastlib/middleware/__init__.py
|
|
42
|
-
fastlib/middleware/
|
|
43
|
-
fastlib/middleware/
|
|
44
|
-
fastlib/middleware/
|
|
42
|
+
fastlib/middleware/db_session.py
|
|
43
|
+
fastlib/middleware/i18n.py
|
|
44
|
+
fastlib/middleware/jwt.py
|
|
45
|
+
fastlib/middleware/log.py
|
|
45
46
|
fastlib/migrate/migrate.py
|
|
46
47
|
fastlib/model/__init__.py
|
|
47
48
|
fastlib/model/base_model.py
|
|
@@ -54,6 +55,7 @@ fastlib/schema/response.py
|
|
|
54
55
|
fastlib/schema/schema.py
|
|
55
56
|
fastlib/security/__init__.py
|
|
56
57
|
fastlib/security/security.py
|
|
58
|
+
fastlib/security/utils.py
|
|
57
59
|
fastlib/service/__init__.py
|
|
58
60
|
fastlib/service/base_service.py
|
|
59
61
|
fastlib/service/impl/__init__.py
|
|
@@ -66,7 +68,6 @@ fastlib/utils/excel_util.py
|
|
|
66
68
|
fastlib/utils/file_util.py
|
|
67
69
|
fastlib/utils/http_util.py
|
|
68
70
|
fastlib/utils/ip_util.py
|
|
69
|
-
fastlib/utils/security_util.py
|
|
70
71
|
fastlib/utils/snowflake_util.py
|
|
71
72
|
fastlib/utils/str_util.py
|
|
72
73
|
fastlib_py.egg-info/PKG-INFO
|