fastlib-py 0.2.5__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 (78) hide show
  1. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/PKG-INFO +1 -1
  2. fastlib_py-0.2.6/fastlib/config/database_config.py +72 -0
  3. fastlib_py-0.2.6/fastlib/config/security_config.py +95 -0
  4. fastlib_py-0.2.6/fastlib/config/server_config.py +48 -0
  5. fastlib_py-0.2.6/fastlib/contextvars.py +58 -0
  6. fastlib_py-0.2.6/fastlib/exception/__init__.py +6 -0
  7. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/exception/base.py +17 -2
  8. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/exception/exception_handler.py +2 -3
  9. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/exception/exception_manager.py +2 -2
  10. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/mapper/impl/base_mapper_impl.py +205 -115
  11. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/middleware/jwt.py +3 -3
  12. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/migrate.py +7 -3
  13. fastlib_py-0.2.6/fastlib/request.py +50 -0
  14. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/response.py +13 -1
  15. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/router.py +17 -20
  16. fastlib_py-0.2.6/fastlib/schema.py +42 -0
  17. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib_py.egg-info/PKG-INFO +1 -1
  18. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib_py.egg-info/SOURCES.txt +1 -1
  19. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/pyproject.toml +1 -1
  20. fastlib_py-0.2.5/fastlib/config/database_config.py +0 -87
  21. fastlib_py-0.2.5/fastlib/config/security_config.py +0 -86
  22. fastlib_py-0.2.5/fastlib/config/server_config.py +0 -57
  23. fastlib_py-0.2.5/fastlib/contextvars.py +0 -7
  24. fastlib_py-0.2.5/fastlib/exception/__init__.py +0 -6
  25. fastlib_py-0.2.5/fastlib/schema.py +0 -81
  26. fastlib_py-0.2.5/fastlib/session/db_session.py +0 -31
  27. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/LICENSE +0 -0
  28. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/README.md +0 -0
  29. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/__init__.py +0 -0
  30. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/cache/__init__.py +0 -0
  31. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/cache/base.py +0 -0
  32. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/cache/disk_cache.py +0 -0
  33. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/cache/manager.py +0 -0
  34. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/cache/redis_cache.py +0 -0
  35. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/__init__.py +0 -0
  36. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/base.py +0 -0
  37. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/loader.py +0 -0
  38. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/manager.py +0 -0
  39. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/registry.py +0 -0
  40. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/config/utils.py +0 -0
  41. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/constant/__init__.py +0 -0
  42. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/constant/constant.py +0 -0
  43. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/enums.py +0 -0
  44. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/i18n/__init__.py +0 -0
  45. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/i18n/manager.py +0 -0
  46. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/i18n/messages.py +0 -0
  47. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/i18n/types.py +0 -0
  48. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/i18n/utils.py +0 -0
  49. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/logging/__init__.py +0 -0
  50. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/logging/config.py +0 -0
  51. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/logging/handlers.py +0 -0
  52. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/mapper/__init__.py +0 -0
  53. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/mapper/base_mapper.py +0 -0
  54. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/mapper/impl/__init__.py +0 -0
  55. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/middleware/__init__.py +0 -0
  56. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/middleware/db_session.py +0 -0
  57. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/middleware/i18n.py +0 -0
  58. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/openapi.py +0 -0
  59. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/security/__init__.py +0 -0
  60. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/security/security.py +0 -0
  61. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/security/utils.py +0 -0
  62. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/service/__init__.py +0 -0
  63. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/service/base_service.py +0 -0
  64. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/service/impl/__init__.py +0 -0
  65. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/service/impl/base_service_impl.py +0 -0
  66. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/session/__init__.py +0 -0
  67. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/session/db_engine.py +0 -0
  68. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/__init__.py +0 -0
  69. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/excel_util.py +0 -0
  70. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/file_util.py +0 -0
  71. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/http_util.py +0 -0
  72. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/ip_util.py +0 -0
  73. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/snowflake_util.py +0 -0
  74. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib/utils/str_util.py +0 -0
  75. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib_py.egg-info/dependency_links.txt +0 -0
  76. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib_py.egg-info/requires.txt +0 -0
  77. {fastlib_py-0.2.5 → fastlib_py-0.2.6}/fastlib_py.egg-info/top_level.txt +0 -0
  78. {fastlib_py-0.2.5 → 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.5
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>
@@ -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")
@@ -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,19 +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
+ """Error detail."""
11
12
 
12
13
  code: int
13
14
  message: str
14
15
 
15
16
 
16
- class HTTPException(Exception):
17
+ class BaseException(Exception):
17
18
  """
18
19
  Base exception class for all custom exception in the application.
19
20
 
@@ -45,3 +46,17 @@ class HTTPException(Exception):
45
46
  def __post_init__(self):
46
47
  if self.message is None:
47
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