fastlib-py 0.2.1__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 (82) hide show
  1. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/PKG-INFO +1 -1
  2. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/cache/manager.py +1 -2
  3. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/constant/__init__.py +1 -0
  4. fastlib_py-0.2.2/fastlib/constant/constant.py +150 -0
  5. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/i18n/manager.py +4 -1
  6. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/i18n/messages.py +23 -18
  7. fastlib_py-0.2.2/fastlib/i18n/types.py +106 -0
  8. fastlib_py-0.2.2/fastlib/i18n/utils.py +43 -0
  9. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/middleware/i18n.py +10 -10
  10. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/middleware/jwt.py +33 -7
  11. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib_py.egg-info/PKG-INFO +1 -1
  12. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/pyproject.toml +1 -1
  13. fastlib_py-0.2.1/fastlib/constant/constant.py +0 -31
  14. fastlib_py-0.2.1/fastlib/i18n/types.py +0 -12
  15. fastlib_py-0.2.1/fastlib/i18n/utils.py +0 -86
  16. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/LICENSE +0 -0
  17. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/README.md +0 -0
  18. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/__init__.py +0 -0
  19. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/cache/__init__.py +0 -0
  20. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/cache/base.py +0 -0
  21. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/cache/disk_cache.py +0 -0
  22. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/cache/redis_cache.py +0 -0
  23. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/__init__.py +0 -0
  24. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/base.py +0 -0
  25. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/database_config.py +0 -0
  26. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/loader.py +0 -0
  27. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/manager.py +0 -0
  28. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/registry.py +0 -0
  29. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/security_config.py +0 -0
  30. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/server_config.py +0 -0
  31. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/config/utils.py +0 -0
  32. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/context/__init__.py +0 -0
  33. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/context/contextvars.py +0 -0
  34. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/enums/__init__.py +0 -0
  35. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/enums/base_error_code.py +0 -0
  36. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/enums/enum.py +0 -0
  37. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/exception/__init__.py +0 -0
  38. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/exception/custom_exception.py +0 -0
  39. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/exception/exception_handler.py +0 -0
  40. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/exception/exception_manager.py +0 -0
  41. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/logging/__init__.py +0 -0
  42. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/logging/config.py +0 -0
  43. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/logging/handlers.py +0 -0
  44. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/mapper/__init__.py +0 -0
  45. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/mapper/base_mapper.py +0 -0
  46. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/mapper/impl/__init__.py +0 -0
  47. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/mapper/impl/base_mapper_impl.py +0 -0
  48. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/middleware/__init__.py +0 -0
  49. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/middleware/db_session.py +0 -0
  50. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/middleware/log.py +0 -0
  51. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/migrate/migrate.py +0 -0
  52. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/model/__init__.py +0 -0
  53. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/model/base_model.py +0 -0
  54. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/openapi/__init__.py +0 -0
  55. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/openapi/offline.py +0 -0
  56. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/router/__init__.py +0 -0
  57. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/router/router_v1.py +0 -0
  58. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/schema/__init__.py +0 -0
  59. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/schema/response.py +0 -0
  60. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/schema/schema.py +0 -0
  61. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/security/__init__.py +0 -0
  62. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/security/security.py +0 -0
  63. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/security/utils.py +0 -0
  64. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/service/__init__.py +0 -0
  65. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/service/base_service.py +0 -0
  66. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/service/impl/__init__.py +0 -0
  67. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/service/impl/base_service_impl.py +0 -0
  68. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/session/__init__.py +0 -0
  69. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/session/db_engine.py +0 -0
  70. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/session/db_session.py +0 -0
  71. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/__init__.py +0 -0
  72. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/excel_util.py +0 -0
  73. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/file_util.py +0 -0
  74. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/http_util.py +0 -0
  75. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/ip_util.py +0 -0
  76. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/snowflake_util.py +0 -0
  77. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib/utils/str_util.py +0 -0
  78. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib_py.egg-info/SOURCES.txt +0 -0
  79. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib_py.egg-info/dependency_links.txt +0 -0
  80. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib_py.egg-info/requires.txt +0 -0
  81. {fastlib_py-0.2.1 → fastlib_py-0.2.2}/fastlib_py.egg-info/top_level.txt +0 -0
  82. {fastlib_py-0.2.1 → 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.1
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>
@@ -2,7 +2,6 @@
2
2
  """Cache Client manager to instantiate the appropriate cache client"""
3
3
 
4
4
  from fastlib.cache.base import Cache
5
-
6
5
  from fastlib.config.manager import ConfigManager
7
6
 
8
7
 
@@ -13,7 +12,7 @@ async def get_cache_client() -> Cache:
13
12
  Cache: Redis client if Redis is enabled in config, otherwise returns page cache.
14
13
  """
15
14
  from fastlib.cache.redis_cache import RedisManager
16
-
15
+
17
16
  config = ConfigManager.get_database_config()
18
17
  if config.enable_redis:
19
18
  from fastlib.cache.redis_cache import RedisCache
@@ -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"
@@ -1,10 +1,13 @@
1
+ # SPDX-License-Identifier: MIT
1
2
  """
2
3
  Internationalization context manager
3
4
  """
4
5
 
5
6
  import contextvars
6
7
  import functools
7
- from typing import Any, Callable, TypeVar, Awaitable
8
+ from collections.abc import Awaitable
9
+ from typing import Any, Callable, TypeVar
10
+
8
11
  from fastlib.i18n.types import Language
9
12
 
10
13
  _language_context = contextvars.ContextVar("language", default=Language.ENGLISH)
@@ -1,32 +1,33 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Load i18n data and export get message method"""
3
+
1
4
  import json
2
5
  from pathlib import Path
3
- from typing import List, Optional, Union
4
- from fastlib.constant.constant import RESOURCE_DIR
6
+ from typing import Optional, Union
7
+
8
+ from fastlib.constant import RESOURCE_DIR
5
9
  from fastlib.i18n.types import Language
6
10
 
7
- # Default i18n directory (assumed to be in the same directory as this file)
8
- DEFAULT_BASE_DIR = Path(RESOURCE_DIR) / "i18n"
11
+ from .manager import get_language
9
12
 
10
- # Global cache for translation data
13
+ DEFAULT_BASE_DIR = Path(RESOURCE_DIR) / "i18n"
11
14
  TRANSLATIONS = {
12
15
  "zh": {"debug": {"test_message": "这是一条调试测试消息"}},
13
16
  "en": {"debug": {"test_message": "This is a debug test message"}},
14
17
  }
15
18
 
16
19
 
17
- def load_translations(base_dirs: Union[Path, List[Path], None] = None):
18
- """Load multilingual translation files from multiple directories.
20
+ def load_translations(base_dirs: Union[Path, list[Path], None] = None):
21
+ """Load translation files from specified directories.
19
22
 
20
23
  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.
24
+ base_dirs: Path or list of paths to translation directories.
23
25
 
24
- File structure requirements:
25
- Each directory should follow: base_dir/{lang}/{namespace}.json
26
- Example: i18n/zh/error.json
26
+ File structure:
27
+ base_dir/{lang}/{namespace}.json
28
+ Example: i18n/en/error.json
27
29
 
28
- Note: Later directories will override translations from earlier directories
29
- if the same language and namespace exist.
30
+ Note: Later directories override earlier ones for duplicate keys.
30
31
  """
31
32
  if base_dirs is None:
32
33
  base_dirs = [DEFAULT_BASE_DIR]
@@ -50,7 +51,7 @@ def load_translations(base_dirs: Union[Path, List[Path], None] = None):
50
51
  namespace = (
51
52
  file.stem
52
53
  ) # Determine message_type from filename, e.g., error, success
53
- with open(file, "r", encoding="utf-8") as f:
54
+ with open(file, encoding="utf-8") as f:
54
55
  translations_data = json.load(f)
55
56
 
56
57
  # Merge with existing namespace data or create new
@@ -60,7 +61,9 @@ def load_translations(base_dirs: Union[Path, List[Path], None] = None):
60
61
  TRANSLATIONS[lang.value][namespace] = translations_data
61
62
 
62
63
 
63
- def get_message(message_type: str, key: str, language: Language) -> str:
64
+ def get_message(
65
+ message_type: str, key: str, language: Optional[Language] = None
66
+ ) -> str:
64
67
  """Get translated message for given type, key and language.
65
68
 
66
69
  Args:
@@ -71,15 +74,17 @@ def get_message(message_type: str, key: str, language: Language) -> str:
71
74
  Returns:
72
75
  Translated message or fallback to default language if not found
73
76
  """
74
- default_language = Language.CHINESE.value
75
77
 
78
+ if not language:
79
+ language = get_language()
76
80
  # Try to get message from requested language
77
81
  lang_dict = TRANSLATIONS.get(language.value, {})
78
82
  msg = lang_dict.get(message_type, {}).get(key)
79
-
83
+ print(f"lang_dict: {lang_dict}")
80
84
  if msg:
81
85
  return msg
82
86
 
87
+ default_language = Language.ENGLISH.value
83
88
  # Fallback to default language
84
89
  return (
85
90
  TRANSLATIONS.get(default_language, {})
@@ -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)
@@ -7,40 +7,40 @@ from fastlib.i18n.types import Language
7
7
  class I18nMiddleware(BaseHTTPMiddleware):
8
8
  """
9
9
  Middleware for automatic language detection and setting based on HTTP headers.
10
-
10
+
11
11
  This middleware inspects the Accept-Language header from incoming requests
12
12
  and sets the appropriate language in the request state for use throughout
13
13
  the application.
14
-
14
+
15
15
  The language preference is stored in request.state.language and can be
16
16
  accessed in route handlers and other middleware.
17
-
17
+
18
18
  Example:
19
19
  ```python
20
20
  from fastapi import FastAPI, Request
21
-
21
+
22
22
  app = FastAPI()
23
23
  app.add_middleware(I18nMiddleware)
24
-
24
+
25
25
  @app.get("/")
26
26
  async def root(request: Request):
27
27
  language = request.state.language
28
28
  return {"language": language.value}
29
29
  ```
30
-
30
+
31
31
  Note:
32
32
  Currently supports only English and Chinese. Defaults to English
33
33
  if the Accept-Language header is not present or unrecognized.
34
34
  """
35
-
35
+
36
36
  async def dispatch(self, request, call_next):
37
37
  """
38
38
  Process the request and set the language based on Accept-Language header.
39
-
39
+
40
40
  Args:
41
41
  request: The incoming HTTP request
42
42
  call_next: The next middleware or route handler in the chain
43
-
43
+
44
44
  Returns:
45
45
  The HTTP response from the next handler
46
46
  """
@@ -54,4 +54,4 @@ class I18nMiddleware(BaseHTTPMiddleware):
54
54
  request.state.language = Language.CHINESE
55
55
 
56
56
  response = await call_next(request)
57
- return response
57
+ return response
@@ -2,6 +2,7 @@
2
2
  """JWT middleware for FastAPI authentication"""
3
3
 
4
4
  import http
5
+ from enum import Enum
5
6
 
6
7
  from fastapi import Request
7
8
  from jwt.exceptions import PyJWTError
@@ -11,7 +12,6 @@ from starlette.responses import JSONResponse
11
12
  from fastlib import ConfigManager, constant, security
12
13
  from fastlib.context.contextvars import current_user_id
13
14
  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
@@ -19,6 +19,32 @@ server_config = ConfigManager.get_server_config()
19
19
  security_config = ConfigManager.get_security_config()
20
20
 
21
21
 
22
+ class JWTErrorCode(Enum):
23
+ """Authentication error codes with code and message"""
24
+
25
+ OPENAPI_FORBIDDEN = (403, "OpenAPI documentation is disabled")
26
+ TOKEN_EXPIRED = (401, "Authorization token has expired")
27
+ MISSING_TOKEN = (401, "Authorization token is missing")
28
+
29
+ def __init__(self, code: int, message: str):
30
+ self._code = code
31
+ self._message = message
32
+
33
+ @property
34
+ def code(self) -> int:
35
+ """Get error code"""
36
+ return self._code
37
+
38
+ @property
39
+ def message(self) -> str:
40
+ """Get error message"""
41
+ return self._message
42
+
43
+ def to_dict(self) -> dict:
44
+ """Convert to dictionary format for JSON response"""
45
+ return {"code": self.code, "message": self.message}
46
+
47
+
22
48
  async def jwt_middleware(request: Request, call_next):
23
49
  ctx_token: UserCredential | None = None
24
50
  try:
@@ -32,8 +58,8 @@ async def jwt_middleware(request: Request, call_next):
32
58
  return JSONResponse(
33
59
  status_code=http.HTTPStatus.FORBIDDEN,
34
60
  content={
35
- "code": AuthErrorCode.OPENAPI_FORBIDDEN.code,
36
- "message": AuthErrorCode.OPENAPI_FORBIDDEN.message,
61
+ "code": JWTErrorCode.OPENAPI_FORBIDDEN.code,
62
+ "message": JWTErrorCode.OPENAPI_FORBIDDEN.message,
37
63
  },
38
64
  )
39
65
 
@@ -63,16 +89,16 @@ async def jwt_middleware(request: Request, call_next):
63
89
  return JSONResponse(
64
90
  status_code=http.HTTPStatus.UNAUTHORIZED,
65
91
  content={
66
- "code": AuthErrorCode.TOKEN_EXPIRED.code,
67
- "message": AuthErrorCode.TOKEN_EXPIRED.message,
92
+ "code": JWTErrorCode.TOKEN_EXPIRED.code,
93
+ "message": JWTErrorCode.TOKEN_EXPIRED.message,
68
94
  },
69
95
  )
70
96
  else:
71
97
  return JSONResponse(
72
98
  status_code=http.HTTPStatus.UNAUTHORIZED,
73
99
  content={
74
- "code": AuthErrorCode.MISSING_TOKEN.code,
75
- "message": AuthErrorCode.MISSING_TOKEN.message,
100
+ "code": JWTErrorCode.MISSING_TOKEN.code,
101
+ "message": JWTErrorCode.MISSING_TOKEN.message,
76
102
  },
77
103
  )
78
104
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastlib-py
3
- Version: 0.2.1
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
  [project]
2
2
  name = "fastlib-py"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Fastlib, high performance, easy to use, fast to code, ready for production"
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -1,31 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- """Common constant"""
3
-
4
- import os
5
-
6
- from fastlib.utils import file_util
7
-
8
- RESOURCE_DIR: str = os.path.abspath(
9
- os.path.join(file_util.find_project_root(), "src", "main", "resource")
10
- )
11
-
12
- ADMIN_ID = 9
13
- ROOT_PARENT_ID = 0
14
- MAX_PAGE_SIZE = 1000
15
-
16
- ENV = "env"
17
- PARENT_ID = "parent_id"
18
- CONFIG_FILE = "config_file"
19
- CONFIG_FILE_NAME = "config.yml"
20
- AUTHORIZATION = "Authorization"
21
-
22
-
23
- class FilterOperators:
24
- EQ = "EQ"
25
- NE = "NE"
26
- GT = "GT"
27
- GE = "GE"
28
- LT = "LT"
29
- LE = "LE"
30
- BETWEEN = "BETWEEN"
31
- LIKE = "LIKE"
@@ -1,12 +0,0 @@
1
- """
2
- Internationalization type definitions
3
- """
4
-
5
- from enum import Enum
6
-
7
-
8
- class Language(Enum):
9
- """Supported language enumeration"""
10
-
11
- CHINESE = "zh"
12
- ENGLISH = "en"
@@ -1,86 +0,0 @@
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
- }
File without changes
File without changes
File without changes