fastlib-py 0.2.4__tar.gz → 0.2.6__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.4 → fastlib_py-0.2.6}/PKG-INFO +1 -1
  2. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/__init__.py +2 -0
  3. fastlib_py-0.2.6/fastlib/config/base.py +8 -0
  4. fastlib_py-0.2.6/fastlib/config/database_config.py +72 -0
  5. fastlib_py-0.2.6/fastlib/config/security_config.py +95 -0
  6. fastlib_py-0.2.6/fastlib/config/server_config.py +48 -0
  7. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/utils.py +1 -1
  8. fastlib_py-0.2.6/fastlib/contextvars.py +58 -0
  9. fastlib_py-0.2.6/fastlib/exception/__init__.py +6 -0
  10. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/base.py +17 -3
  11. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/exception_handler.py +2 -3
  12. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/exception_manager.py +2 -2
  13. fastlib_py-0.2.6/fastlib/i18n/__init__.py +1 -0
  14. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/manager.py +5 -5
  15. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/logging/__init__.py +4 -1
  16. fastlib_py-0.2.6/fastlib/logging/config.py +102 -0
  17. fastlib_py-0.2.6/fastlib/logging/handlers.py +172 -0
  18. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/mapper/impl/base_mapper_impl.py +205 -115
  19. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/jwt.py +4 -4
  20. {fastlib_py-0.2.4/fastlib/migrate → fastlib_py-0.2.6/fastlib}/migrate.py +7 -3
  21. fastlib_py-0.2.6/fastlib/request.py +50 -0
  22. {fastlib_py-0.2.4/fastlib/schema → fastlib_py-0.2.6/fastlib}/response.py +17 -3
  23. fastlib_py-0.2.4/fastlib/router/base.py → fastlib_py-0.2.6/fastlib/router.py +17 -20
  24. fastlib_py-0.2.6/fastlib/schema.py +42 -0
  25. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/session/db_engine.py +0 -2
  26. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/PKG-INFO +1 -1
  27. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/SOURCES.txt +9 -13
  28. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/pyproject.toml +126 -126
  29. fastlib_py-0.2.4/fastlib/config/base.py +0 -17
  30. fastlib_py-0.2.4/fastlib/config/database_config.py +0 -87
  31. fastlib_py-0.2.4/fastlib/config/security_config.py +0 -86
  32. fastlib_py-0.2.4/fastlib/config/server_config.py +0 -57
  33. fastlib_py-0.2.4/fastlib/context/contextvars.py +0 -7
  34. fastlib_py-0.2.4/fastlib/enums/__init__.py +0 -9
  35. fastlib_py-0.2.4/fastlib/exception/__init__.py +0 -6
  36. fastlib_py-0.2.4/fastlib/logging/config.py +0 -75
  37. fastlib_py-0.2.4/fastlib/logging/handlers.py +0 -221
  38. fastlib_py-0.2.4/fastlib/openapi/__init__.py +0 -5
  39. fastlib_py-0.2.4/fastlib/router/__init__.py +0 -5
  40. fastlib_py-0.2.4/fastlib/schema/__init__.py +0 -19
  41. fastlib_py-0.2.4/fastlib/schema/base.py +0 -82
  42. fastlib_py-0.2.4/fastlib/service/impl/__init__.py +0 -0
  43. fastlib_py-0.2.4/fastlib/session/db_session.py +0 -31
  44. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/LICENSE +0 -0
  45. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/README.md +0 -0
  46. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/__init__.py +0 -0
  47. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/base.py +0 -0
  48. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/disk_cache.py +0 -0
  49. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/manager.py +0 -0
  50. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/redis_cache.py +0 -0
  51. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/__init__.py +0 -0
  52. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/loader.py +0 -0
  53. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/manager.py +0 -0
  54. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/registry.py +0 -0
  55. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/constant/__init__.py +0 -0
  56. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/constant/constant.py +0 -0
  57. /fastlib_py-0.2.4/fastlib/enums/base.py → /fastlib_py-0.2.6/fastlib/enums.py +0 -0
  58. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/messages.py +0 -0
  59. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/types.py +0 -0
  60. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/utils.py +0 -0
  61. {fastlib_py-0.2.4/fastlib/context → fastlib_py-0.2.6/fastlib/mapper}/__init__.py +0 -0
  62. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/mapper/base_mapper.py +0 -0
  63. {fastlib_py-0.2.4/fastlib/mapper → fastlib_py-0.2.6/fastlib/mapper/impl}/__init__.py +0 -0
  64. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/__init__.py +0 -0
  65. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/db_session.py +0 -0
  66. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/i18n.py +0 -0
  67. /fastlib_py-0.2.4/fastlib/openapi/offline.py → /fastlib_py-0.2.6/fastlib/openapi.py +0 -0
  68. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/__init__.py +0 -0
  69. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/security.py +0 -0
  70. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/utils.py +0 -0
  71. {fastlib_py-0.2.4/fastlib/mapper/impl → fastlib_py-0.2.6/fastlib/service}/__init__.py +0 -0
  72. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/service/base_service.py +0 -0
  73. {fastlib_py-0.2.4/fastlib/service → fastlib_py-0.2.6/fastlib/service/impl}/__init__.py +0 -0
  74. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/service/impl/base_service_impl.py +0 -0
  75. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/session/__init__.py +0 -0
  76. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/__init__.py +0 -0
  77. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/excel_util.py +0 -0
  78. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/file_util.py +0 -0
  79. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/http_util.py +0 -0
  80. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/ip_util.py +0 -0
  81. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/snowflake_util.py +0 -0
  82. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/str_util.py +0 -0
  83. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/dependency_links.txt +0 -0
  84. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/requires.txt +0 -0
  85. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/top_level.txt +0 -0
  86. {fastlib_py-0.2.4 → fastlib_py-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastlib-py
3
- Version: 0.2.4
3
+ Version: 0.2.6
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>
@@ -5,10 +5,12 @@ from .config import ConfigManager as ConfigManager
5
5
  from .constant import constant as constant
6
6
  from .logging import LogConfig as LogConfig
7
7
  from .logging import Logger as Logger
8
+ from .openapi import register_offline_openapi as register_offline_openapi
8
9
 
9
10
  __all__ = [
10
11
  "ConfigManager",
11
12
  "Logger",
12
13
  "LogConfig",
13
14
  "constant",
15
+ "register_offline_openapi",
14
16
  ]
@@ -0,0 +1,8 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Base configuration classes."""
3
+
4
+
5
+ class BaseConfig:
6
+ """Base class for all configuration classes."""
7
+
8
+ pass
@@ -0,0 +1,72 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Database configuration for the application."""
3
+
4
+ import os
5
+ from dataclasses import dataclass, field
6
+
7
+ import fastlib.config.utils as config_util
8
+ from fastlib.config.base import BaseConfig
9
+
10
+
11
+ @dataclass
12
+ class DatabaseConfig(BaseConfig):
13
+ """
14
+ Database configuration for the application.
15
+
16
+ Attributes:
17
+ dialect: The database dialect (e.g., mysql, postgresql, sqlite).
18
+ url: The database connection URL. Overrides environment variable DATABASE_URL.
19
+ pool_size: The number of connections to keep in the pool. Default: 10.
20
+ max_overflow: The maximum number of connections beyond pool_size. Default: 20.
21
+ pool_recycle: Connection recycle time in seconds. Default: 3600 (1 hour).
22
+ pool_pre_ping: Whether to test connections for liveness. Default: True.
23
+ echo_sql: Whether to log SQL statements. Default: False.
24
+ enable_redis: Whether to enable Redis cache. Default: False.
25
+ cache_host: Redis host address. Default: localhost.
26
+ cache_port: Redis port number. Default: 6379.
27
+ cache_pass: Redis password. Default: empty.
28
+ cache_db_num: Redis database number. Default: 0.
29
+ """
30
+
31
+ # Database configuration
32
+ dialect: str = field(default_factory=lambda: config_util.get_db_dialect() or "")
33
+ url: str = field(default_factory=lambda: DatabaseConfig._get_default_db_url())
34
+ pool_size: int = 10
35
+ max_overflow: int = 20
36
+ pool_recycle: int = 3600
37
+ pool_pre_ping: bool = True
38
+ echo_sql: bool = False
39
+
40
+ # Redis/cache configuration
41
+ enable_redis: bool = False
42
+ cache_host: str = "localhost"
43
+ cache_port: int = 6379
44
+ cache_pass: str = field(default_factory=lambda: os.getenv("REDIS_PASSWORD", ""))
45
+ cache_db_num: int = 0
46
+
47
+ def __post_init__(self) -> None:
48
+ """Post-initialization processing."""
49
+ # Handle dialect default
50
+ if not self.dialect.strip():
51
+ self.dialect = config_util.get_db_dialect()
52
+
53
+ # Handle URL priority: environment variable > parameter > default
54
+ env_db_url = os.getenv("DB_URL")
55
+ if env_db_url:
56
+ self.url = env_db_url
57
+ elif not self.url.strip():
58
+ self.url = self._get_default_db_url()
59
+
60
+ # Handle Redis password from environment
61
+ env_redis_pass = os.getenv("REDIS_PASSWORD")
62
+ if env_redis_pass is not None:
63
+ self.cache_pass = env_redis_pass
64
+
65
+ @staticmethod
66
+ def _get_default_db_url() -> str:
67
+ """Get default database URL based on dialect."""
68
+ dialect = config_util.get_db_dialect()
69
+ if dialect == "sqlite" or dialect is None:
70
+ return config_util.get_sqlite_db_path()
71
+ else:
72
+ return config_util.get_db_url()
@@ -0,0 +1,95 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Security configuration for the application."""
3
+
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from typing import ClassVar
7
+
8
+ from fastlib.config.base import BaseConfig
9
+
10
+
11
+ @dataclass
12
+ class SecurityConfig(BaseConfig):
13
+ """
14
+ Security configuration for the application.
15
+
16
+ Attributes:
17
+ enable: Whether to enable security features. Default: True.
18
+ algorithm: The encryption algorithm used for token generation. Default: HS256.
19
+ secret_key: The secret key used for signing the tokens. Default: placeholder.
20
+ access_token_expire_minutes: Minutes until access token expires. Default: 30.
21
+ refresh_token_expire_minutes: Minutes until refresh token expires. Default: 10080 (7 days).
22
+ enable_swagger: Whether to enable swagger UI. Default: False.
23
+ white_list_routes: Comma-separated routes accessible without authentication.
24
+ Default: API docs and health endpoints.
25
+ backend_cors_origins: Comma-separated list of allowed CORS origins.
26
+ Default: Local development addresses.
27
+ black_ip_list: Comma-separated list of blocked IP addresses. Default: empty.
28
+ """
29
+
30
+ enable: bool = True
31
+ algorithm: str = "HS256"
32
+ secret_key: str = field(
33
+ default_factory=lambda: os.getenv(
34
+ "SECRET_KEY", "your-secret-key-change-in-production"
35
+ )
36
+ )
37
+ access_token_expire_minutes: int = 30
38
+ refresh_token_expire_minutes: int = 60 * 24 * 7 # 7 days
39
+ enable_swagger: bool = False
40
+ white_list_routes: str = "/api/v1/probe/liveness, /api/v1/user/register, /api/v1/auth:signInWithEmailAndPassword"
41
+ backend_cors_origins: str = (
42
+ "http://127.0.0.1:8000, http://localhost:8000, http://localhost"
43
+ )
44
+ black_ip_list: str = ""
45
+
46
+ # Class-level constants for better organization
47
+ _DEFAULT_WHITE_LIST_ROUTES: ClassVar[str] = (
48
+ "/api/v1/probe/liveness, /api/v1/user/register, /api/v1/auth:signInWithEmailAndPassword"
49
+ )
50
+ _DEFAULT_CORS_ORIGINS: ClassVar[str] = (
51
+ "http://127.0.0.1:8000, http://localhost:8000, http://localhost"
52
+ )
53
+
54
+ def __post_init__(self) -> None:
55
+ """Post-initialization processing."""
56
+ # Ensure secret_key uses environment variable if available
57
+ env_secret_key = os.getenv("SECRET_KEY")
58
+ if env_secret_key is not None:
59
+ self.secret_key = env_secret_key
60
+
61
+ @property
62
+ def white_list_routes_list(self) -> list[str]:
63
+ """Returns white list routes as a list."""
64
+ return [
65
+ route.strip()
66
+ for route in self.white_list_routes.split(",")
67
+ if route.strip()
68
+ ]
69
+
70
+ @property
71
+ def backend_cors_origins_list(self) -> list[str]:
72
+ """Returns CORS origins as a list."""
73
+ return [
74
+ origin.strip()
75
+ for origin in self.backend_cors_origins.split(",")
76
+ if origin.strip()
77
+ ]
78
+
79
+ @property
80
+ def black_ip_list_list(self) -> list[str]:
81
+ """Returns blacklisted IPs as a list."""
82
+ return [ip.strip() for ip in self.black_ip_list.split(",") if ip.strip()]
83
+
84
+ def __str__(self) -> str:
85
+ """
86
+ Returns a string representation of the security configuration.
87
+
88
+ Returns:
89
+ A string representation of the SecurityConfig instance.
90
+ """
91
+ # Hide secret key in string representation for security
92
+ safe_dict = self.__dict__.copy()
93
+ if "secret_key" in safe_dict:
94
+ safe_dict["secret_key"] = "***" if safe_dict["secret_key"] else ""
95
+ return f"{self.__class__.__name__}({safe_dict})"
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Server configuration for the application."""
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import ClassVar
6
+
7
+ from fastlib.config.base import BaseConfig
8
+ from fastlib.config.utils import ProjectInfo
9
+
10
+
11
+ @dataclass
12
+ class ServerConfig(BaseConfig):
13
+ """
14
+ Server configuration for the application.
15
+
16
+ Attributes:
17
+ host: The server host address.
18
+ port: The server port number.
19
+ name: The server name.
20
+ version: The server version.
21
+ app_desc: The server description.
22
+ api_prefix: The API prefix for routes.
23
+ debug: Whether to enable debug mode.
24
+ workers: The number of worker processes.
25
+ enable_rate_limit: Whether to enable rate limiting.
26
+ global_default_limits: Global rate limit setting.
27
+ """
28
+
29
+ _project_config: ClassVar[ProjectInfo] = ProjectInfo.from_pyproject()
30
+
31
+ host: str = "127.0.0.1"
32
+ port: int = 18888
33
+ name: str = field(default_factory=lambda: ServerConfig._get_default_name())
34
+ version: str = field(default_factory=lambda: ServerConfig._project_config.version)
35
+ app_desc: str = field(
36
+ default_factory=lambda: ServerConfig._project_config.description
37
+ )
38
+ api_prefix: str = "/api"
39
+ debug: bool = False
40
+ workers: int = 1
41
+ enable_rate_limit: bool = False
42
+ global_default_limits: str = "10/second"
43
+
44
+ @classmethod
45
+ def _get_default_name(cls) -> str:
46
+ """Get the default server name from project config."""
47
+ name = cls._project_config.name
48
+ return name[0].upper() + name[1:] if name else "Server"
@@ -8,7 +8,7 @@ from urllib.parse import urlparse
8
8
 
9
9
  from loguru import logger
10
10
 
11
- from fastlib.enums.base import DBTypeEnum
11
+ from fastlib.enums import DBTypeEnum
12
12
  from fastlib.utils import file_util
13
13
 
14
14
  try:
@@ -0,0 +1,58 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Context variable for storing current user authentication context.
3
+
4
+ This module provides a thread-local and async-safe way to store and access
5
+ the current authenticated user's ID throughout the request lifecycle.
6
+ """
7
+
8
+ from contextvars import ContextVar
9
+ from typing import Any, Optional
10
+
11
+ from loguru import logger
12
+
13
+ # Type aliases for better readability
14
+ UserID = Optional[int]
15
+
16
+ # Context variable declaration
17
+ _current_user_id: ContextVar[UserID] = ContextVar("current_user_id", default=None)
18
+
19
+
20
+ def set_current_user(user_id: int) -> Any:
21
+ """Set the current user ID in context.
22
+
23
+ Args:
24
+ user_id: The authenticated user's ID (must be positive integer)
25
+
26
+ Returns:
27
+ Token that can be used to reset the context
28
+
29
+ Raises:
30
+ ValueError: If user_id is not a positive integer
31
+ """
32
+ if not isinstance(user_id, int) or user_id <= 0:
33
+ raise ValueError("user_id must be a positive integer")
34
+
35
+ logger.debug("Setting current user context: user_id=%s", user_id)
36
+ return _current_user_id.set(user_id)
37
+
38
+
39
+ def get_current_user() -> UserID:
40
+ """Get the current user ID from context.
41
+
42
+ Returns:
43
+ The current user ID or None if not set
44
+ """
45
+ user_id = _current_user_id.get()
46
+ if user_id is None:
47
+ logger.warning("Attempted to get current user ID but context is not set")
48
+ return user_id
49
+
50
+
51
+ def clear_current_user(token: Any) -> None:
52
+ """Reset the current user context.
53
+
54
+ Args:
55
+ token: The token returned by set_current_user
56
+ """
57
+ _current_user_id.reset(token)
58
+ logger.debug("Cleared current user context")
@@ -0,0 +1,6 @@
1
+ """Exception handling."""
2
+
3
+ from .base import BaseException, ErrorDetail
4
+ from .exception_manager import register_exception_handlers
5
+
6
+ __all__ = [BaseException, ErrorDetail, register_exception_handlers]
@@ -1,20 +1,20 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  """Base exception class for the application."""
3
3
 
4
+ from http import HTTPStatus
4
5
  from typing import Any, Optional
5
6
 
6
7
  from pydantic import BaseModel
7
8
 
8
9
 
9
10
  class ErrorDetail(BaseModel):
10
- """Base class for error code.
11
- """
11
+ """Error detail."""
12
12
 
13
13
  code: int
14
14
  message: str
15
15
 
16
16
 
17
- class HTTPException(Exception):
17
+ class BaseException(Exception):
18
18
  """
19
19
  Base exception class for all custom exception in the application.
20
20
 
@@ -46,3 +46,17 @@ class HTTPException(Exception):
46
46
  def __post_init__(self):
47
47
  if self.message is None:
48
48
  self.message = self.code.message
49
+
50
+
51
+ class SystemErrorCode:
52
+ """System-related error codes."""
53
+
54
+ INTERNAL_ERROR = ErrorDetail(
55
+ code=HTTPStatus.INTERNAL_SERVER_ERROR.value, message="Internal server error"
56
+ )
57
+
58
+
59
+ class SystemException(BaseException):
60
+ code: SystemErrorCode
61
+ message: Optional[str] = None
62
+ details: Optional[Any] = None
@@ -16,7 +16,7 @@ from pydantic_core._pydantic_core import ValidationError # noqa
16
16
  from starlette.exceptions import HTTPException as StarletteHTTPException
17
17
 
18
18
  from fastlib import ConfigManager
19
- from fastlib.exception import HTTPException
19
+ from fastlib.exception import BaseException
20
20
 
21
21
 
22
22
  async def extract_request_data(request: Request) -> dict[str, Any]:
@@ -163,7 +163,7 @@ def is_auth_errors_code(error_code: int):
163
163
  return False
164
164
 
165
165
 
166
- async def custom_exception_handler(request: Request, exc: HTTPException):
166
+ async def custom_exception_handler(request: Request, exc: BaseException):
167
167
  """
168
168
  Handler for custom HTTPException.
169
169
  """
@@ -240,7 +240,6 @@ async def custom_http_exception_handler(request: Request, exc: StarletteHTTPExce
240
240
  return await http_exception_handler(request, exc)
241
241
 
242
242
 
243
- # 修改为:
244
243
  async def jwt_exception_handler(request: Request):
245
244
  """
246
245
  Handler for JWT-related exceptions.
@@ -8,7 +8,7 @@ from pydantic_core._pydantic_core import ValidationError # noqa
8
8
  from starlette.exceptions import HTTPException as StarletteHTTPException
9
9
 
10
10
  from fastlib.exception import exception_handler
11
- from fastlib.exception.base import HTTPException
11
+ from fastlib.exception.base import BaseException
12
12
 
13
13
 
14
14
  def register_exception_handlers(app: FastAPI) -> None:
@@ -20,7 +20,7 @@ def register_exception_handlers(app: FastAPI) -> None:
20
20
  ValidationError, exception_handler.validation_exception_handler
21
21
  )
22
22
 
23
- app.add_exception_handler(HTTPException, exception_handler.custom_exception_handler)
23
+ app.add_exception_handler(BaseException, exception_handler.custom_exception_handler)
24
24
 
25
25
  app.add_exception_handler(
26
26
  StarletteHTTPException, exception_handler.custom_http_exception_handler
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: MIT
@@ -56,11 +56,6 @@ class I18nContext:
56
56
  _language_context.reset(self._token)
57
57
 
58
58
 
59
- def with_language(language: Language) -> I18nContext:
60
- """Helper to create context manager"""
61
- return I18nContext(language)
62
-
63
-
64
59
  def _language_context_decorator(
65
60
  language: Language, is_async: bool = False
66
61
  ) -> Callable[[F], F]:
@@ -97,3 +92,8 @@ def async_language_context(
97
92
  ) -> Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]]:
98
93
  """Decorator: run an async function within a language context"""
99
94
  return _language_context_decorator(language, is_async=True)
95
+
96
+
97
+ def i18n_context(language: Language) -> I18nContext:
98
+ """Helper to create context manager"""
99
+ return I18nContext(language)
@@ -1,4 +1,7 @@
1
- """Logging module"""
1
+ # SPDX-License-Identifier: MIT
2
+ """
3
+ Logging module
4
+ """
2
5
 
3
6
  from .config import LogConfig
4
7
  from .handlers import Logger
@@ -0,0 +1,102 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """
3
+ Log configuration and logging utility for the application.
4
+ """
5
+
6
+ from pathlib import Path
7
+ from typing import Literal, Optional
8
+
9
+ from fastlib.config.base import BaseConfig
10
+ from fastlib.config.manager import config_class
11
+ from fastlib.config.utils import ProjectInfo
12
+
13
+ LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
14
+
15
+
16
+ @config_class("log")
17
+ class LogConfig(BaseConfig):
18
+ """Configuration for logging settings."""
19
+
20
+ def __init__(
21
+ self,
22
+ log_dir: str = "/var/log",
23
+ app_name: str = None,
24
+ log_level: LogLevel = "INFO",
25
+ log_rotation: str = "1 day",
26
+ log_retention: str = "90 days",
27
+ enable_console_log: bool = True,
28
+ enable_json_logs: bool = False,
29
+ error_log_rotation: str = "1 day",
30
+ error_log_retention: str = "90 days",
31
+ enable_async: bool = True,
32
+ max_file_size: Optional[str] = "10 MB",
33
+ time_format: str = "YYYY-MM-DD",
34
+ **kwargs,
35
+ ):
36
+ """
37
+ Initialize LogConfig with logging settings.
38
+
39
+ Args:
40
+ log_dir: Directory where log files will be stored
41
+ app_name: Name of the application for log file naming
42
+ log_level: Minimum log level to record
43
+ log_rotation: How often to rotate log files
44
+ log_retention: How long to keep log files
45
+ enable_console_log: Whether to output logs to console
46
+ enable_json_logs: Whether to use JSON format for logs
47
+ error_log_rotation: How often to rotate error log files
48
+ error_log_retention: How long to keep error log files
49
+ enable_async: Whether to use asynchronous logging
50
+ max_file_size: Maximum size for log files before rotation
51
+ time_format: Format string for timestamps in log file names
52
+ **kwargs: Additional keyword arguments for base class
53
+ """
54
+ # Set app_name default if not provided
55
+ if app_name is None:
56
+ app_name = ProjectInfo.from_pyproject().name
57
+
58
+ # Initialize instance attributes directly
59
+ self.log_dir = log_dir
60
+ self.app_name = app_name
61
+ self.log_level = log_level
62
+ self.log_rotation = log_rotation
63
+ self.log_retention = log_retention
64
+ self.enable_console_log = enable_console_log
65
+ self.enable_json_logs = enable_json_logs
66
+ self.error_log_rotation = error_log_rotation
67
+ self.error_log_retention = error_log_retention
68
+ self.enable_async = enable_async
69
+ self.max_file_size = max_file_size
70
+ self.time_format = time_format
71
+
72
+ # Handle any additional kwargs for base class
73
+ if kwargs:
74
+ # If base class needs special handling, you can add it here
75
+ # For now, we'll set them as instance attributes
76
+ for key, value in kwargs.items():
77
+ setattr(self, key, value)
78
+
79
+ @property
80
+ def default_format(self) -> str:
81
+ """Default log format string."""
82
+ return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message}"
83
+
84
+ @property
85
+ def log_file_pattern(self) -> str:
86
+ """Returns the main log file pattern with timestamp."""
87
+ return f"{{time:{self.time_format}}}_{self.app_name}.log"
88
+
89
+ @property
90
+ def error_log_file_pattern(self) -> str:
91
+ """Returns the error log file pattern with timestamp."""
92
+ return f"{{time:{self.time_format}}}_error_{self.app_name}.log"
93
+
94
+ @property
95
+ def log_file_path(self) -> Path:
96
+ """Returns the full path for the main log file."""
97
+ return Path(self.log_dir) / self.log_file_pattern
98
+
99
+ @property
100
+ def error_log_file_path(self) -> Path:
101
+ """Returns the full path for the error log file."""
102
+ return Path(self.log_dir) / self.error_log_file_pattern