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.
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/PKG-INFO +1 -1
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/__init__.py +2 -0
- fastlib_py-0.2.6/fastlib/config/base.py +8 -0
- fastlib_py-0.2.6/fastlib/config/database_config.py +72 -0
- fastlib_py-0.2.6/fastlib/config/security_config.py +95 -0
- fastlib_py-0.2.6/fastlib/config/server_config.py +48 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/utils.py +1 -1
- fastlib_py-0.2.6/fastlib/contextvars.py +58 -0
- fastlib_py-0.2.6/fastlib/exception/__init__.py +6 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/base.py +17 -3
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/exception_handler.py +2 -3
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/exception/exception_manager.py +2 -2
- fastlib_py-0.2.6/fastlib/i18n/__init__.py +1 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/manager.py +5 -5
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/logging/__init__.py +4 -1
- fastlib_py-0.2.6/fastlib/logging/config.py +102 -0
- fastlib_py-0.2.6/fastlib/logging/handlers.py +172 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/mapper/impl/base_mapper_impl.py +205 -115
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/jwt.py +4 -4
- {fastlib_py-0.2.4/fastlib/migrate → fastlib_py-0.2.6/fastlib}/migrate.py +7 -3
- fastlib_py-0.2.6/fastlib/request.py +50 -0
- {fastlib_py-0.2.4/fastlib/schema → fastlib_py-0.2.6/fastlib}/response.py +17 -3
- fastlib_py-0.2.4/fastlib/router/base.py → fastlib_py-0.2.6/fastlib/router.py +17 -20
- fastlib_py-0.2.6/fastlib/schema.py +42 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/session/db_engine.py +0 -2
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/PKG-INFO +1 -1
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/SOURCES.txt +9 -13
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/pyproject.toml +126 -126
- fastlib_py-0.2.4/fastlib/config/base.py +0 -17
- fastlib_py-0.2.4/fastlib/config/database_config.py +0 -87
- fastlib_py-0.2.4/fastlib/config/security_config.py +0 -86
- fastlib_py-0.2.4/fastlib/config/server_config.py +0 -57
- fastlib_py-0.2.4/fastlib/context/contextvars.py +0 -7
- fastlib_py-0.2.4/fastlib/enums/__init__.py +0 -9
- fastlib_py-0.2.4/fastlib/exception/__init__.py +0 -6
- fastlib_py-0.2.4/fastlib/logging/config.py +0 -75
- fastlib_py-0.2.4/fastlib/logging/handlers.py +0 -221
- fastlib_py-0.2.4/fastlib/openapi/__init__.py +0 -5
- fastlib_py-0.2.4/fastlib/router/__init__.py +0 -5
- fastlib_py-0.2.4/fastlib/schema/__init__.py +0 -19
- fastlib_py-0.2.4/fastlib/schema/base.py +0 -82
- fastlib_py-0.2.4/fastlib/service/impl/__init__.py +0 -0
- fastlib_py-0.2.4/fastlib/session/db_session.py +0 -31
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/LICENSE +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/README.md +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/base.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/disk_cache.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/manager.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/cache/redis_cache.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/loader.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/manager.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/config/registry.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/constant/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/constant/constant.py +0 -0
- /fastlib_py-0.2.4/fastlib/enums/base.py → /fastlib_py-0.2.6/fastlib/enums.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/messages.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/types.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/i18n/utils.py +0 -0
- {fastlib_py-0.2.4/fastlib/context → fastlib_py-0.2.6/fastlib/mapper}/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/mapper/base_mapper.py +0 -0
- {fastlib_py-0.2.4/fastlib/mapper → fastlib_py-0.2.6/fastlib/mapper/impl}/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/db_session.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/middleware/i18n.py +0 -0
- /fastlib_py-0.2.4/fastlib/openapi/offline.py → /fastlib_py-0.2.6/fastlib/openapi.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/security.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/security/utils.py +0 -0
- {fastlib_py-0.2.4/fastlib/mapper/impl → fastlib_py-0.2.6/fastlib/service}/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/service/base_service.py +0 -0
- {fastlib_py-0.2.4/fastlib/service → fastlib_py-0.2.6/fastlib/service/impl}/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/service/impl/base_service_impl.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/session/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/__init__.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/excel_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/file_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/http_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/ip_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/snowflake_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib/utils/str_util.py +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/dependency_links.txt +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/requires.txt +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/fastlib_py.egg-info/top_level.txt +0 -0
- {fastlib_py-0.2.4 → fastlib_py-0.2.6}/setup.cfg +0 -0
|
@@ -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,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"
|
|
@@ -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")
|
|
@@ -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
|
-
"""
|
|
11
|
-
"""
|
|
11
|
+
"""Error detail."""
|
|
12
12
|
|
|
13
13
|
code: int
|
|
14
14
|
message: str
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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)
|
|
@@ -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
|