fastlib-py 0.2.0__tar.gz → 0.2.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.
Files changed (86) hide show
  1. {fastlib_py-0.2.0/fastlib_py.egg-info → fastlib_py-0.2.2}/PKG-INFO +1 -1
  2. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/cache/__init__.py +1 -1
  3. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/cache/disk_cache.py +2 -2
  4. fastlib_py-0.2.0/fastlib/cache/cache_manager.py → fastlib_py-0.2.2/fastlib/cache/manager.py +2 -2
  5. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/cache/redis_cache.py +6 -7
  6. fastlib_py-0.2.2/fastlib/config/base.py +17 -0
  7. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/utils.py +0 -2
  8. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/constant/__init__.py +1 -0
  9. fastlib_py-0.2.2/fastlib/constant/constant.py +150 -0
  10. fastlib_py-0.2.2/fastlib/i18n/manager.py +99 -0
  11. fastlib_py-0.2.2/fastlib/i18n/messages.py +93 -0
  12. fastlib_py-0.2.2/fastlib/i18n/types.py +106 -0
  13. fastlib_py-0.2.2/fastlib/i18n/utils.py +43 -0
  14. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/logging/config.py +9 -0
  15. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/mapper/impl/base_mapper_impl.py +1 -1
  16. fastlib_py-0.2.2/fastlib/middleware/__init__.py +7 -0
  17. fastlib_py-0.2.2/fastlib/middleware/i18n.py +57 -0
  18. fastlib_py-0.2.0/fastlib/middleware/jwt_middleware.py → fastlib_py-0.2.2/fastlib/middleware/jwt.py +33 -7
  19. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/model/base_model.py +2 -2
  20. {fastlib_py-0.2.0 → fastlib_py-0.2.2/fastlib_py.egg-info}/PKG-INFO +1 -1
  21. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib_py.egg-info/SOURCES.txt +11 -10
  22. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/pyproject.toml +1 -1
  23. fastlib_py-0.2.0/fastlib/config/base.py +0 -6
  24. fastlib_py-0.2.0/fastlib/constant/constant.py +0 -31
  25. fastlib_py-0.2.0/fastlib/i18n/i18n_context.py +0 -215
  26. fastlib_py-0.2.0/fastlib/i18n/i18n_messages.py +0 -99
  27. fastlib_py-0.2.0/fastlib/i18n/i18n_types.py +0 -12
  28. fastlib_py-0.2.0/fastlib/i18n/i18n_util.py +0 -233
  29. fastlib_py-0.2.0/fastlib/middleware/__init__.py +0 -7
  30. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/LICENSE +0 -0
  31. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/README.md +0 -0
  32. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/__init__.py +0 -0
  33. /fastlib_py-0.2.0/fastlib/cache/base_cache.py → /fastlib_py-0.2.2/fastlib/cache/base.py +0 -0
  34. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/__init__.py +0 -0
  35. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/database_config.py +0 -0
  36. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/loader.py +0 -0
  37. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/manager.py +0 -0
  38. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/registry.py +0 -0
  39. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/security_config.py +0 -0
  40. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/config/server_config.py +0 -0
  41. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/context/__init__.py +0 -0
  42. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/context/contextvars.py +0 -0
  43. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/enums/__init__.py +0 -0
  44. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/enums/base_error_code.py +0 -0
  45. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/enums/enum.py +0 -0
  46. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/exception/__init__.py +0 -0
  47. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/exception/custom_exception.py +0 -0
  48. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/exception/exception_handler.py +0 -0
  49. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/exception/exception_manager.py +0 -0
  50. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/logging/__init__.py +0 -0
  51. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/logging/handlers.py +0 -0
  52. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/mapper/__init__.py +0 -0
  53. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/mapper/base_mapper.py +0 -0
  54. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/mapper/impl/__init__.py +0 -0
  55. /fastlib_py-0.2.0/fastlib/middleware/db_session_middleware.py → /fastlib_py-0.2.2/fastlib/middleware/db_session.py +0 -0
  56. /fastlib_py-0.2.0/fastlib/middleware/log_middleware.py → /fastlib_py-0.2.2/fastlib/middleware/log.py +0 -0
  57. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/migrate/migrate.py +0 -0
  58. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/model/__init__.py +0 -0
  59. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/openapi/__init__.py +0 -0
  60. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/openapi/offline.py +0 -0
  61. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/router/__init__.py +0 -0
  62. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/router/router_v1.py +0 -0
  63. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/schema/__init__.py +0 -0
  64. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/schema/response.py +0 -0
  65. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/schema/schema.py +0 -0
  66. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/security/__init__.py +0 -0
  67. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/security/security.py +0 -0
  68. /fastlib_py-0.2.0/fastlib/utils/security_util.py → /fastlib_py-0.2.2/fastlib/security/utils.py +0 -0
  69. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/service/__init__.py +0 -0
  70. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/service/base_service.py +0 -0
  71. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/service/impl/__init__.py +0 -0
  72. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/service/impl/base_service_impl.py +0 -0
  73. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/session/__init__.py +0 -0
  74. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/session/db_engine.py +0 -0
  75. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/session/db_session.py +0 -0
  76. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/__init__.py +0 -0
  77. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/excel_util.py +0 -0
  78. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/file_util.py +0 -0
  79. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/http_util.py +0 -0
  80. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/ip_util.py +0 -0
  81. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/snowflake_util.py +0 -0
  82. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib/utils/str_util.py +0 -0
  83. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib_py.egg-info/dependency_links.txt +0 -0
  84. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib_py.egg-info/requires.txt +0 -0
  85. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/fastlib_py.egg-info/top_level.txt +0 -0
  86. {fastlib_py-0.2.0 → fastlib_py-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastlib-py
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Fastlib, high performance, easy to use, fast to code, ready for production
5
5
  Author-email: tyvekZhang <tyvekzhang@gmail.com>
6
6
  Maintainer-email: tyvekZhang <tyvekzhang@gmail.com>
@@ -1,6 +1,6 @@
1
1
  """Provides a unified cache client based on configuration."""
2
2
 
3
- from .cache_manager import get_cache_client
3
+ from .manager import get_cache_client
4
4
 
5
5
  __all__ = [
6
6
  get_cache_client,
@@ -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, list
8
+ from typing import Any, Optional
9
9
 
10
10
  import diskcache
11
11
 
12
- from fastlib.cache.base_cache import Cache, CacheError
12
+ from fastlib.cache.base import Cache, CacheError
13
13
 
14
14
 
15
15
  class PageCache(Cache):
@@ -1,8 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  """Cache Client manager to instantiate the appropriate cache client"""
3
3
 
4
- from fastlib.cache.base_cache import Cache
5
- from fastlib.cache.redis_cache import RedisManager
4
+ from fastlib.cache.base import Cache
6
5
  from fastlib.config.manager import ConfigManager
7
6
 
8
7
 
@@ -12,6 +11,7 @@ async def get_cache_client() -> Cache:
12
11
  Returns:
13
12
  Cache: Redis client if Redis is enabled in config, otherwise returns page cache.
14
13
  """
14
+ from fastlib.cache.redis_cache import RedisManager
15
15
 
16
16
  config = ConfigManager.get_database_config()
17
17
  if config.enable_redis:
@@ -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, list
8
+ from typing import Any, Optional
9
9
 
10
10
  from loguru import logger
11
11
 
12
- from fastlib.cache.base_cache import Cache, CacheError
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.error(
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
- database = load_config().database
349
+ database_config = ConfigManager.get_database_config()
351
350
  cls._connection_pool = redis.ConnectionPool.from_url(
352
- f"redis://:{database.cache_pass}@{database.cache_host}:{database.cache_port}/{database.db_num}",
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
 
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: MIT
1
2
  """Constant for the application"""
2
3
 
3
4
  from .constant import (
@@ -0,0 +1,150 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Common constant"""
3
+
4
+ import os
5
+ from typing import Any
6
+
7
+ from fastlib.utils import file_util
8
+
9
+ # Default constant values
10
+ _DEFAULT_CONSTANTS: dict[str, Any] = {
11
+ "RESOURCE_DIR": os.path.abspath(
12
+ os.path.join(file_util.find_project_root(), "src", "main", "resource")
13
+ ),
14
+ "ADMIN_ID": 9,
15
+ "ROOT_PARENT_ID": 0,
16
+ "MAX_PAGE_SIZE": 1000,
17
+ "ENV": "env",
18
+ "PARENT_ID": "parent_id",
19
+ "CONFIG_FILE": "config_file",
20
+ "CONFIG_FILE_NAME": "config.yml",
21
+ "AUTHORIZATION": "Authorization",
22
+ }
23
+
24
+ # Current constant values (initially defaults)
25
+ _CURRENT_CONSTANTS = _DEFAULT_CONSTANTS.copy()
26
+
27
+ # Exported constants (maintain original interface)
28
+ RESOURCE_DIR: str = _CURRENT_CONSTANTS["RESOURCE_DIR"]
29
+ ADMIN_ID = _CURRENT_CONSTANTS["ADMIN_ID"]
30
+ ROOT_PARENT_ID = _CURRENT_CONSTANTS["ROOT_PARENT_ID"]
31
+ MAX_PAGE_SIZE = _CURRENT_CONSTANTS["MAX_PAGE_SIZE"]
32
+ ENV = _CURRENT_CONSTANTS["ENV"]
33
+ PARENT_ID = _CURRENT_CONSTANTS["PARENT_ID"]
34
+ CONFIG_FILE = _CURRENT_CONSTANTS["CONFIG_FILE"]
35
+ CONFIG_FILE_NAME = _CURRENT_CONSTANTS["CONFIG_FILE_NAME"]
36
+ AUTHORIZATION = _CURRENT_CONSTANTS["AUTHORIZATION"]
37
+
38
+
39
+ def set_constant(name: str, value: Any) -> None:
40
+ """Set constant value.
41
+
42
+ Args:
43
+ name: Constant name
44
+ value: New value
45
+
46
+ Raises:
47
+ ValueError: If constant doesn't exist
48
+ """
49
+ if name not in _DEFAULT_CONSTANTS:
50
+ raise ValueError(f"Constant '{name}' doesn't exist")
51
+
52
+ _CURRENT_CONSTANTS[name] = value
53
+ _update_exported_constants()
54
+
55
+
56
+ def reset_constant(name: str = None) -> None:
57
+ """Reset constant(s) to default values.
58
+
59
+ Args:
60
+ name: Constant name to reset, None resets all
61
+
62
+ Raises:
63
+ ValueError: If constant doesn't exist
64
+ """
65
+ if name is None:
66
+ _CURRENT_CONSTANTS.clear()
67
+ _CURRENT_CONSTANTS.update(_DEFAULT_CONSTANTS)
68
+ else:
69
+ if name not in _DEFAULT_CONSTANTS:
70
+ raise ValueError(f"Constant '{name}' doesn't exist")
71
+ _CURRENT_CONSTANTS[name] = _DEFAULT_CONSTANTS[name]
72
+
73
+ _update_exported_constants()
74
+
75
+
76
+ def get_constant(name: str) -> Any:
77
+ """Get current constant value.
78
+
79
+ Args:
80
+ name: Constant name
81
+
82
+ Returns:
83
+ Current constant value
84
+
85
+ Raises:
86
+ ValueError: If constant doesn't exist
87
+ """
88
+ if name not in _CURRENT_CONSTANTS:
89
+ raise ValueError(f"Constant '{name}' doesn't exist")
90
+ return _CURRENT_CONSTANTS[name]
91
+
92
+
93
+ def get_default_constant(name: str) -> Any:
94
+ """Get default constant value.
95
+
96
+ Args:
97
+ name: Constant name
98
+
99
+ Returns:
100
+ Default constant value
101
+
102
+ Raises:
103
+ ValueError: If constant doesn't exist
104
+ """
105
+ if name not in _DEFAULT_CONSTANTS:
106
+ raise ValueError(f"Constant '{name}' doesn't exist")
107
+ return _DEFAULT_CONSTANTS[name]
108
+
109
+
110
+ def list_constants() -> dict[str, dict[str, Any]]:
111
+ """List all constants with current and default values.
112
+
113
+ Returns:
114
+ Dictionary with constant information
115
+ """
116
+ return {
117
+ name: {
118
+ "current": _CURRENT_CONSTANTS[name],
119
+ "default": _DEFAULT_CONSTANTS[name],
120
+ "is_modified": _CURRENT_CONSTANTS[name] != _DEFAULT_CONSTANTS[name],
121
+ }
122
+ for name in _DEFAULT_CONSTANTS
123
+ }
124
+
125
+
126
+ def _update_exported_constants() -> None:
127
+ """Update exported constant variables."""
128
+ global RESOURCE_DIR, ADMIN_ID, ROOT_PARENT_ID, MAX_PAGE_SIZE
129
+ global ENV, PARENT_ID, CONFIG_FILE, CONFIG_FILE_NAME, AUTHORIZATION
130
+
131
+ RESOURCE_DIR = _CURRENT_CONSTANTS["RESOURCE_DIR"]
132
+ ADMIN_ID = _CURRENT_CONSTANTS["ADMIN_ID"]
133
+ ROOT_PARENT_ID = _CURRENT_CONSTANTS["ROOT_PARENT_ID"]
134
+ MAX_PAGE_SIZE = _CURRENT_CONSTANTS["MAX_PAGE_SIZE"]
135
+ ENV = _CURRENT_CONSTANTS["ENV"]
136
+ PARENT_ID = _CURRENT_CONSTANTS["PARENT_ID"]
137
+ CONFIG_FILE = _CURRENT_CONSTANTS["CONFIG_FILE"]
138
+ CONFIG_FILE_NAME = _CURRENT_CONSTANTS["CONFIG_FILE_NAME"]
139
+ AUTHORIZATION = _CURRENT_CONSTANTS["AUTHORIZATION"]
140
+
141
+
142
+ class FilterOperators:
143
+ EQ = "EQ"
144
+ NE = "NE"
145
+ GT = "GT"
146
+ GE = "GE"
147
+ LT = "LT"
148
+ LE = "LE"
149
+ BETWEEN = "BETWEEN"
150
+ LIKE = "LIKE"
@@ -0,0 +1,99 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """
3
+ Internationalization context manager
4
+ """
5
+
6
+ import contextvars
7
+ import functools
8
+ from collections.abc import Awaitable
9
+ from typing import Any, Callable, TypeVar
10
+
11
+ from fastlib.i18n.types import Language
12
+
13
+ _language_context = contextvars.ContextVar("language", default=Language.ENGLISH)
14
+
15
+ F = TypeVar("F", bound=Callable[..., Any])
16
+
17
+
18
+ def set_language(language: Language) -> None:
19
+ """Set current language"""
20
+ _language_context.set(language)
21
+
22
+
23
+ def get_language() -> Language:
24
+ """Get current language"""
25
+ return _language_context.get()
26
+
27
+
28
+ def get_language_code() -> str:
29
+ """Get current language code, e.g. 'en', 'zh'"""
30
+ return get_language().value
31
+
32
+
33
+ def reset_language() -> None:
34
+ """Reset language to default"""
35
+ _language_context.set(Language.ENGLISH)
36
+
37
+
38
+ def is_language(language: Language) -> bool:
39
+ """Check if current language matches"""
40
+ return get_language() == language
41
+
42
+
43
+ class I18nContext:
44
+ """Context manager to temporarily switch language"""
45
+
46
+ def __init__(self, language: Language):
47
+ self.language = language
48
+ self._token: contextvars.Token | None = None
49
+
50
+ def __enter__(self):
51
+ self._token = _language_context.set(self.language)
52
+ return self
53
+
54
+ def __exit__(self, exc_type, exc_val, exc_tb):
55
+ if self._token is not None:
56
+ _language_context.reset(self._token)
57
+
58
+
59
+ def with_language(language: Language) -> I18nContext:
60
+ """Helper to create context manager"""
61
+ return I18nContext(language)
62
+
63
+
64
+ def _language_context_decorator(
65
+ language: Language, is_async: bool = False
66
+ ) -> Callable[[F], F]:
67
+ """Internal generator for sync/async language context decorators"""
68
+
69
+ def decorator(func: F) -> F:
70
+ if is_async:
71
+
72
+ @functools.wraps(func)
73
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
74
+ with I18nContext(language):
75
+ return await func(*args, **kwargs)
76
+
77
+ return async_wrapper
78
+ else:
79
+
80
+ @functools.wraps(func)
81
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
82
+ with I18nContext(language):
83
+ return func(*args, **kwargs)
84
+
85
+ return wrapper
86
+
87
+ return decorator
88
+
89
+
90
+ def language_context(language: Language) -> Callable[[F], F]:
91
+ """Decorator: run a sync function within a language context"""
92
+ return _language_context_decorator(language, is_async=False)
93
+
94
+
95
+ def async_language_context(
96
+ language: Language,
97
+ ) -> Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]]:
98
+ """Decorator: run an async function within a language context"""
99
+ return _language_context_decorator(language, is_async=True)
@@ -0,0 +1,93 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Load i18n data and export get message method"""
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Optional, Union
7
+
8
+ from fastlib.constant import RESOURCE_DIR
9
+ from fastlib.i18n.types import Language
10
+
11
+ from .manager import get_language
12
+
13
+ DEFAULT_BASE_DIR = Path(RESOURCE_DIR) / "i18n"
14
+ TRANSLATIONS = {
15
+ "zh": {"debug": {"test_message": "这是一条调试测试消息"}},
16
+ "en": {"debug": {"test_message": "This is a debug test message"}},
17
+ }
18
+
19
+
20
+ def load_translations(base_dirs: Union[Path, list[Path], None] = None):
21
+ """Load translation files from specified directories.
22
+
23
+ Args:
24
+ base_dirs: Path or list of paths to translation directories.
25
+
26
+ File structure:
27
+ base_dir/{lang}/{namespace}.json
28
+ Example: i18n/en/error.json
29
+
30
+ Note: Later directories override earlier ones for duplicate keys.
31
+ """
32
+ if base_dirs is None:
33
+ base_dirs = [DEFAULT_BASE_DIR]
34
+ elif isinstance(base_dirs, Path):
35
+ base_dirs = [base_dirs]
36
+
37
+ for base_dir in base_dirs:
38
+ if not base_dir.exists():
39
+ continue # Skip if directory doesn't exist
40
+
41
+ for lang in [Language.CHINESE, Language.ENGLISH]:
42
+ lang_dir = base_dir / lang.value
43
+ if not lang_dir.exists():
44
+ continue # Skip if language directory doesn't exist
45
+
46
+ # Initialize language dict if not exists
47
+ if lang.value not in TRANSLATIONS:
48
+ TRANSLATIONS[lang.value] = {}
49
+
50
+ for file in lang_dir.glob("*.json"):
51
+ namespace = (
52
+ file.stem
53
+ ) # Determine message_type from filename, e.g., error, success
54
+ with open(file, encoding="utf-8") as f:
55
+ translations_data = json.load(f)
56
+
57
+ # Merge with existing namespace data or create new
58
+ if namespace in TRANSLATIONS[lang.value]:
59
+ TRANSLATIONS[lang.value][namespace].update(translations_data)
60
+ else:
61
+ TRANSLATIONS[lang.value][namespace] = translations_data
62
+
63
+
64
+ def get_message(
65
+ message_type: str, key: str, language: Optional[Language] = None
66
+ ) -> str:
67
+ """Get translated message for given type, key and language.
68
+
69
+ Args:
70
+ message_type: Namespace/category of the message (e.g., 'error', 'success')
71
+ key: Message identifier key
72
+ language: Target language
73
+
74
+ Returns:
75
+ Translated message or fallback to default language if not found
76
+ """
77
+
78
+ if not language:
79
+ language = get_language()
80
+ # Try to get message from requested language
81
+ lang_dict = TRANSLATIONS.get(language.value, {})
82
+ msg = lang_dict.get(message_type, {}).get(key)
83
+ print(f"lang_dict: {lang_dict}")
84
+ if msg:
85
+ return msg
86
+
87
+ default_language = Language.ENGLISH.value
88
+ # Fallback to default language
89
+ return (
90
+ TRANSLATIONS.get(default_language, {})
91
+ .get(message_type, {})
92
+ .get(key, f"Unknown {message_type} message: {key}")
93
+ )
@@ -0,0 +1,106 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """
3
+ Internationalization type definitions with extensible language support
4
+ """
5
+
6
+ from enum import Enum
7
+ from typing import Optional
8
+
9
+
10
+ class Language(Enum):
11
+ """Extensible language enumeration with metadata support"""
12
+
13
+ CHINESE = "zh"
14
+ ENGLISH = "en"
15
+
16
+ # Language metadata (can be extended)
17
+ _metadata: dict[str, dict[str, str]] = {
18
+ "zh": {"name": "Chinese", "native_name": "中文", "direction": "ltr"},
19
+ "en": {"name": "English", "native_name": "English", "direction": "ltr"},
20
+ }
21
+
22
+ @property
23
+ def display_name(self) -> str:
24
+ """Get language display name"""
25
+ return self._metadata[self.value]["name"]
26
+
27
+ @property
28
+ def native_name(self) -> str:
29
+ """Get native language name"""
30
+ return self._metadata[self.value]["native_name"]
31
+
32
+ @property
33
+ def direction(self) -> str:
34
+ """Get text direction (ltr/rtl)"""
35
+ return self._metadata[self.value]["direction"]
36
+
37
+ @classmethod
38
+ def register(
39
+ cls, code: str, name: str, native_name: str, direction: str = "ltr"
40
+ ) -> None:
41
+ """Register a new language dynamically
42
+
43
+ Args:
44
+ code: Language code (e.g., 'es', 'fr')
45
+ name: English display name
46
+ native_name: Native language name
47
+ direction: Text direction ('ltr' or 'rtl')
48
+ """
49
+ if hasattr(cls, code.upper()):
50
+ raise ValueError(f"Language {code} already registered")
51
+
52
+ # Create new enum member
53
+ new_member = Enum.value.__new__(cls)
54
+ new_member._value_ = code
55
+ cls._member_map_[code.upper()] = new_member
56
+ cls._member_names_.append(code.upper())
57
+
58
+ # Add metadata
59
+ cls._metadata[code] = {
60
+ "name": name,
61
+ "native_name": native_name,
62
+ "direction": direction,
63
+ }
64
+
65
+ @classmethod
66
+ def from_code(cls, code: str) -> Optional["Language"]:
67
+ """Get Language enum from language code
68
+
69
+ Args:
70
+ code: Language code (e.g., 'zh', 'en')
71
+
72
+ Returns:
73
+ Language enum or None if not found
74
+ """
75
+ code_upper = code.upper()
76
+ return cls._member_map_.get(code_upper)
77
+
78
+ @classmethod
79
+ def supported_codes(cls) -> set[str]:
80
+ """Get set of all supported language codes"""
81
+ return {member.value for member in cls}
82
+
83
+ @classmethod
84
+ def is_supported(cls, code: str) -> bool:
85
+ """Check if language code is supported"""
86
+ return code in cls.supported_codes()
87
+
88
+
89
+ # if __name__ == "__main__":
90
+ # # Register new languages dynamically
91
+ # Language.register("es", "Spanish", "Español")
92
+ # Language.register("ja", "Japanese", "日本語")
93
+ # Language.register("ar", "Arabic", "العربية", "rtl")
94
+
95
+ # # Access new languages
96
+ # spanish = Language.ES
97
+ # print(f"Spanish code: {spanish.value}") # es
98
+ # print(f"Spanish native: {spanish.native_name}") # Español
99
+
100
+ # # Check support
101
+ # print(Language.is_supported("fr")) # False
102
+ # print(Language.is_supported("ja")) # True
103
+
104
+ # # Get from code
105
+ # japanese = Language.from_code("ja")
106
+ # print(japanese.display_name) # Japanese
@@ -0,0 +1,43 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """
3
+ Internationalization utilities
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ from .manager import I18nContext
9
+ from .messages import get_message
10
+ from .types import Language
11
+
12
+ # Default language
13
+ DEFAULT_LANGUAGE = Language.ENGLISH
14
+
15
+ # Language alias mapping
16
+ LANGUAGE_MAPPING = {
17
+ "zh": Language.CHINESE,
18
+ "zh_cn": Language.CHINESE,
19
+ "en": Language.ENGLISH,
20
+ "en_us": Language.ENGLISH,
21
+ }
22
+
23
+
24
+ def _resolve_language(language: Optional[Language]) -> Language:
25
+ """Return given language or current context default"""
26
+ return language or I18nContext.get_language()
27
+
28
+
29
+ def parse_language(language_str: Optional[str]) -> Language:
30
+ """Parse string like 'zh-CN', 'en-US' to Language enum"""
31
+ if not language_str:
32
+ return DEFAULT_LANGUAGE
33
+ return LANGUAGE_MAPPING.get(
34
+ language_str.lower().replace("-", "_"), DEFAULT_LANGUAGE
35
+ )
36
+
37
+
38
+ def get_localized_message(
39
+ category: str, key: str, language: Optional[Language] = None
40
+ ) -> str:
41
+ """Get localized message by category ('error', 'success', 'business')"""
42
+ lang = _resolve_language(language)
43
+ return get_message(category, key, lang)
@@ -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.db_session_middleware import db
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,7 @@
1
+ """Export middleware symbols."""
2
+
3
+ from .db_session import SQLAlchemyMiddleware, db
4
+ from .jwt import jwt_middleware
5
+ from .log import log_requests
6
+
7
+ __all__ = [SQLAlchemyMiddleware, jwt_middleware, log_requests, db]