fastlib-py 0.3.8__tar.gz → 0.4.0__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.3.8 → fastlib_py-0.4.0}/PKG-INFO +1 -2
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/__init__.py +0 -2
- fastlib_py-0.4.0/fastlib/cache/_memony_cache.py +67 -0
- fastlib_py-0.4.0/fastlib/cache/_redis_cache.py +118 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/__init__.py +2 -1
- fastlib_py-0.4.0/fastlib/config/_log_config.py +102 -0
- fastlib_py-0.4.0/fastlib/config/decorator.py +25 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/loader.py +0 -4
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/manager.py +15 -21
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/utils.py +3 -5
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/contextvars.py +1 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/exception_handler.py +1 -1
- fastlib_py-0.4.0/fastlib/logging/__init__.py +10 -0
- {fastlib_py-0.3.8/fastlib/_logging → fastlib_py-0.4.0/fastlib/logging}/handlers.py +2 -2
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/jwt.py +1 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/migrate.py +1 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/router.py +2 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/handler.py +1 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/sse.py +1 -3
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/excel_util.py +2 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/http_util.py +2 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/PKG-INFO +1 -2
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/SOURCES.txt +4 -4
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/requires.txt +0 -1
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/pyproject.toml +3 -4
- fastlib_py-0.3.8/fastlib/_logging/config.py +0 -109
- fastlib_py-0.3.8/fastlib/cache/_memony_cache.py +0 -35
- fastlib_py-0.3.8/fastlib/cache/_redis_cache.py +0 -58
- fastlib_py-0.3.8/fastlib/logging.py +0 -8
- fastlib_py-0.3.8/fastlib/service/impl/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/LICENSE +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/README.md +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/cache/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/cache/manager.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_database_config.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_security_config.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_server_config.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/base.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/registry.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/constants.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/db_engine.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/enums.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/base.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/exception_manager.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/manager.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/messages.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/types.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/utils.py +0 -0
- {fastlib_py-0.3.8/fastlib/_logging → fastlib_py-0.4.0/fastlib/mapper}/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/mapper/base_mapper.py +0 -0
- {fastlib_py-0.3.8/fastlib/mapper → fastlib_py-0.4.0/fastlib/mapper/impl}/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/mapper/impl/base_mapper_impl.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/db_session.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/i18n.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/openapi.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/py.typed +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/request.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/response.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/schema.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/security.py +0 -0
- {fastlib_py-0.3.8/fastlib/mapper/impl → fastlib_py-0.4.0/fastlib/service}/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/service/base_service.py +0 -0
- {fastlib_py-0.3.8/fastlib/service → fastlib_py-0.4.0/fastlib/service/impl}/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/service/impl/base_service_impl.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/enums.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/event.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/schema.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/__init__.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/file_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/ip_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/security_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/snowflake_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/str_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/validate_util.py +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/dependency_links.txt +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/top_level.txt +0 -0
- {fastlib_py-0.3.8 → fastlib_py-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastlib-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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>
|
|
@@ -21,7 +21,6 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: aiosqlite>=0.21
|
|
23
23
|
Requires-Dist: alembic>=1.15.2
|
|
24
|
-
Requires-Dist: fakeredis>=2.32
|
|
25
24
|
Requires-Dist: fastapi[standard]>=0.115.12
|
|
26
25
|
Requires-Dist: loguru>=0.7.3
|
|
27
26
|
Requires-Dist: openpyxl>=3.1.5
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
"""Fastlib Application Libraries Package."""
|
|
3
3
|
|
|
4
|
-
from ._logging.config import LogConfig as LogConfig
|
|
5
4
|
from .config import ConfigManager as ConfigManager
|
|
6
5
|
from .openapi import register_offline_openapi as register_offline_openapi
|
|
7
6
|
|
|
8
7
|
__all__ = [
|
|
9
8
|
"ConfigManager",
|
|
10
|
-
"LogConfig",
|
|
11
9
|
"register_offline_openapi",
|
|
12
10
|
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Memory (MemoryStorage) Cache Manager for in-memory caching"""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MemoryStorage:
|
|
8
|
+
"""In-memory storage implementation"""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self._data = {}
|
|
12
|
+
|
|
13
|
+
async def set(self, key, value):
|
|
14
|
+
self._data[key] = value
|
|
15
|
+
return key
|
|
16
|
+
|
|
17
|
+
async def get(self, key):
|
|
18
|
+
return self._data.get(key)
|
|
19
|
+
|
|
20
|
+
async def update(self, key, **updated):
|
|
21
|
+
if key not in self._data:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
value = self._data[key]
|
|
25
|
+
for key, value in updated.items():
|
|
26
|
+
if hasattr(value, key):
|
|
27
|
+
setattr(value, key, value)
|
|
28
|
+
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
async def delete(self, key):
|
|
32
|
+
if key in self._data:
|
|
33
|
+
del self._data[key]
|
|
34
|
+
return True
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
async def list(self):
|
|
38
|
+
return list(self._data.values())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MemoryCacheManager:
|
|
42
|
+
"""Singleton manager for in-memory (MemoryStorage) cache client."""
|
|
43
|
+
|
|
44
|
+
_instance = None
|
|
45
|
+
_lock = asyncio.Lock()
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
async def get_instance(cls):
|
|
49
|
+
"""Get a singleton MemoryStorage instance."""
|
|
50
|
+
if cls._instance is None:
|
|
51
|
+
async with cls._lock:
|
|
52
|
+
if cls._instance is None:
|
|
53
|
+
cls._instance = MemoryStorage()
|
|
54
|
+
return cls._instance
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
async def close(cls):
|
|
58
|
+
"""Close the MemoryStorage instance (for cleanup in tests)."""
|
|
59
|
+
if cls._instance is not None:
|
|
60
|
+
await cls._instance.close()
|
|
61
|
+
cls._instance = None
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
async def reset(cls):
|
|
65
|
+
"""Clear all cached data without destroying the instance."""
|
|
66
|
+
if cls._instance is not None:
|
|
67
|
+
await cls._instance.flushall()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Redis cache implementation"""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
from datetime import date, datetime
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from redis._parsers.encoders import Encoder
|
|
11
|
+
|
|
12
|
+
from fastlib import ConfigManager
|
|
13
|
+
from fastlib.logging.handlers import logger
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import redis.asyncio as redis
|
|
17
|
+
except ModuleNotFoundError:
|
|
18
|
+
logger.warning(
|
|
19
|
+
"Cannot find redis module, please install it via `uv add redis[hiredis]`"
|
|
20
|
+
)
|
|
21
|
+
redis = None
|
|
22
|
+
RedisError = Exception
|
|
23
|
+
except Exception:
|
|
24
|
+
logger.error("Error importing redis module")
|
|
25
|
+
redis = None
|
|
26
|
+
RedisError = Exception
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CustomJsonEncoder(Encoder):
|
|
30
|
+
def encode(self, value: Any):
|
|
31
|
+
"""Extended encoding to support datetime and Pydantic models."""
|
|
32
|
+
if isinstance(value, datetime | date):
|
|
33
|
+
return json.dumps(
|
|
34
|
+
{
|
|
35
|
+
"__type__": "datetime",
|
|
36
|
+
"value": value.isoformat(),
|
|
37
|
+
}
|
|
38
|
+
).encode("utf-8")
|
|
39
|
+
|
|
40
|
+
elif isinstance(value, BaseModel):
|
|
41
|
+
return json.dumps(
|
|
42
|
+
{
|
|
43
|
+
"__type__": "pydantic",
|
|
44
|
+
"model": value.__class__.__name__,
|
|
45
|
+
"module": value.__class__.__module__,
|
|
46
|
+
"data": value.model_dump(),
|
|
47
|
+
}
|
|
48
|
+
).encode("utf-8")
|
|
49
|
+
|
|
50
|
+
elif isinstance(value, dict):
|
|
51
|
+
return json.dumps(value).encode("utf-8")
|
|
52
|
+
|
|
53
|
+
return super().encode(value)
|
|
54
|
+
|
|
55
|
+
def decode(self, value, force=False):
|
|
56
|
+
"""Extended decoding to restore datetime and Pydantic models."""
|
|
57
|
+
if self.decode_responses or force:
|
|
58
|
+
if isinstance(value, bytes):
|
|
59
|
+
value = value.decode(self.encoding, self.encoding_errors)
|
|
60
|
+
try:
|
|
61
|
+
data = json.loads(value)
|
|
62
|
+
if isinstance(data, dict) and "__type__" in data:
|
|
63
|
+
if data["__type__"] == "datetime":
|
|
64
|
+
return datetime.fromisoformat(data["value"])
|
|
65
|
+
|
|
66
|
+
elif data["__type__"] == "pydantic":
|
|
67
|
+
module_name = data.get("module")
|
|
68
|
+
model_name = data.get("model")
|
|
69
|
+
payload = data.get("data")
|
|
70
|
+
|
|
71
|
+
# Dynamically import model class
|
|
72
|
+
module = __import__(module_name, fromlist=[model_name])
|
|
73
|
+
model_cls: type[BaseModel] = getattr(module, model_name)
|
|
74
|
+
return model_cls(**payload)
|
|
75
|
+
|
|
76
|
+
return data
|
|
77
|
+
except json.JSONDecodeError:
|
|
78
|
+
raise
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RedisCacheManager:
|
|
83
|
+
_instance: Optional["redis.Redis"] = None
|
|
84
|
+
_connection_pool: Optional["redis.ConnectionPool"] = None
|
|
85
|
+
_lock = asyncio.Lock()
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
async def get_instance(cls) -> "redis.Redis":
|
|
89
|
+
"""
|
|
90
|
+
Get redis instance
|
|
91
|
+
"""
|
|
92
|
+
if redis is None:
|
|
93
|
+
raise RuntimeError(
|
|
94
|
+
"Redis module is not available. Please install it via 'uv add redis[hiredis]'"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if cls._instance is None:
|
|
98
|
+
async with cls._lock:
|
|
99
|
+
if cls._connection_pool is None:
|
|
100
|
+
database_config = ConfigManager.get_database_config()
|
|
101
|
+
cls._connection_pool = redis.ConnectionPool.from_url(
|
|
102
|
+
f"redis://:{database_config.cache_pass}@{database_config.cache_host}:{database_config.cache_port}/{database_config.db_num}",
|
|
103
|
+
encoder_class=CustomJsonEncoder,
|
|
104
|
+
encoding="utf-8",
|
|
105
|
+
encoding_errors="strict",
|
|
106
|
+
decode_responses=True,
|
|
107
|
+
)
|
|
108
|
+
if cls._instance is None:
|
|
109
|
+
cls._instance = redis.Redis(connection_pool=cls._connection_pool)
|
|
110
|
+
return cls._instance
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
async def close_pool(cls):
|
|
114
|
+
"""Close the connection pool."""
|
|
115
|
+
if cls._connection_pool:
|
|
116
|
+
await cls._connection_pool.disconnect()
|
|
117
|
+
cls._connection_pool = None
|
|
118
|
+
cls._instance = None
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Log configuration and logging utility for the application.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal, Optional
|
|
9
|
+
|
|
10
|
+
from fastlib.config.base import BaseConfig
|
|
11
|
+
from fastlib.config.utils import ProjectInfo
|
|
12
|
+
|
|
13
|
+
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
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
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Decorator for easy registration"""
|
|
3
|
+
|
|
4
|
+
from fastlib.config.base import BaseConfig
|
|
5
|
+
from fastlib.config.registry import ConfigRegistry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def config_class(name: str):
|
|
9
|
+
"""
|
|
10
|
+
Decorator to register a configuration class.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
name: The name to register the configuration under
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
@config_class("my_config")
|
|
17
|
+
class MyConfig(BaseConfig):
|
|
18
|
+
pass
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def decorator(cls: type[BaseConfig]):
|
|
22
|
+
ConfigRegistry.register(name, cls)
|
|
23
|
+
return cls
|
|
24
|
+
|
|
25
|
+
return decorator
|
|
@@ -5,7 +5,6 @@ import os
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
import yaml
|
|
8
|
-
from loguru import logger
|
|
9
8
|
|
|
10
9
|
from fastlib import constants as constant
|
|
11
10
|
from fastlib.config import utils as config_util
|
|
@@ -48,9 +47,6 @@ class ConfigLoader:
|
|
|
48
47
|
yaml.YAMLError: If there is an error parsing the YAML
|
|
49
48
|
"""
|
|
50
49
|
if not os.path.exists(file_path):
|
|
51
|
-
logger.warning(
|
|
52
|
-
f"Config file not found: {file_path}, will use the default config"
|
|
53
|
-
)
|
|
54
50
|
return {}
|
|
55
51
|
|
|
56
52
|
with open(file_path, encoding="utf-8") as file:
|
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
from fastlib import constants as constant
|
|
8
8
|
from fastlib.config import utils as config_util
|
|
9
9
|
from fastlib.config._database_config import DatabaseConfig
|
|
10
|
+
from fastlib.config._log_config import LogConfig
|
|
10
11
|
from fastlib.config._security_config import SecurityConfig
|
|
11
12
|
from fastlib.config._server_config import ServerConfig
|
|
12
13
|
from fastlib.config.base import BaseConfig
|
|
@@ -14,27 +15,6 @@ from fastlib.config.loader import ConfigLoader
|
|
|
14
15
|
from fastlib.config.registry import ConfigRegistry
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
# Decorator for easy registration
|
|
18
|
-
def config_class(name: str):
|
|
19
|
-
"""
|
|
20
|
-
Decorator to register a configuration class.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
name: The name to register the configuration under
|
|
24
|
-
|
|
25
|
-
Usage:
|
|
26
|
-
@config_class("my_config")
|
|
27
|
-
class MyConfig(BaseConfig):
|
|
28
|
-
pass
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def decorator(cls: type[BaseConfig]):
|
|
32
|
-
ConfigRegistry.register(name, cls)
|
|
33
|
-
return cls
|
|
34
|
-
|
|
35
|
-
return decorator
|
|
36
|
-
|
|
37
|
-
|
|
38
18
|
class ConfigManager:
|
|
39
19
|
"""Global configuration manager with static methods and dynamic registry support."""
|
|
40
20
|
|
|
@@ -108,6 +88,7 @@ class ConfigManager:
|
|
|
108
88
|
"server": ServerConfig,
|
|
109
89
|
"database": DatabaseConfig,
|
|
110
90
|
"security": SecurityConfig,
|
|
91
|
+
"log": LogConfig,
|
|
111
92
|
}
|
|
112
93
|
|
|
113
94
|
for name, config_class in default_configs.items():
|
|
@@ -194,6 +175,19 @@ class ConfigManager:
|
|
|
194
175
|
raise RuntimeError("Security configuration not properly initialized")
|
|
195
176
|
return instance
|
|
196
177
|
|
|
178
|
+
@staticmethod
|
|
179
|
+
def get_log_config() -> LogConfig:
|
|
180
|
+
"""
|
|
181
|
+
Get the log configuration.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
LogConfig: The log configuration object
|
|
185
|
+
"""
|
|
186
|
+
instance = ConfigManager.get_config_instance("log")
|
|
187
|
+
if not isinstance(instance, LogConfig):
|
|
188
|
+
raise RuntimeError("Log configuration not properly initialized")
|
|
189
|
+
return instance
|
|
190
|
+
|
|
197
191
|
@staticmethod
|
|
198
192
|
def is_initialized() -> bool:
|
|
199
193
|
"""
|
|
@@ -6,8 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any, NamedTuple
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
8
|
|
|
9
|
-
from loguru import logger
|
|
10
|
-
|
|
11
9
|
from fastlib.enums import DBTypeEnum
|
|
12
10
|
from fastlib.utils import file_util
|
|
13
11
|
|
|
@@ -16,8 +14,8 @@ try:
|
|
|
16
14
|
except ModuleNotFoundError:
|
|
17
15
|
import toml as tomllib
|
|
18
16
|
except Exception:
|
|
19
|
-
|
|
20
|
-
"Cannot find
|
|
17
|
+
print(
|
|
18
|
+
"Cannot find toml module, please install it via `uv add toml` or `pip install toml`"
|
|
21
19
|
)
|
|
22
20
|
|
|
23
21
|
|
|
@@ -175,7 +173,7 @@ def get_db_url() -> str:
|
|
|
175
173
|
try:
|
|
176
174
|
return get_alembic_db_info(file_util.get_file_path("alembic.ini")).full_url
|
|
177
175
|
except FileNotFoundError:
|
|
178
|
-
|
|
176
|
+
print("Can not found alembic.ini in the project, use default db url")
|
|
179
177
|
return "sqlite+aiosqlite:///./fastlib.db"
|
|
180
178
|
|
|
181
179
|
|
|
@@ -8,7 +8,7 @@ the current authenticated user's ID throughout the request lifecycle.
|
|
|
8
8
|
from contextvars import ContextVar
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from fastlib.logging.handlers import logger
|
|
12
12
|
|
|
13
13
|
# Type aliases for better readability
|
|
14
14
|
UserID = int | None
|
|
@@ -11,12 +11,12 @@ from fastapi.exception_handlers import http_exception_handler
|
|
|
11
11
|
from fastapi.exceptions import RequestValidationError
|
|
12
12
|
from fastapi.responses import JSONResponse, Response
|
|
13
13
|
from fastapi.utils import is_body_allowed_for_status_code
|
|
14
|
-
from loguru import logger
|
|
15
14
|
from pydantic_core._pydantic_core import ValidationError # noqa
|
|
16
15
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
17
16
|
|
|
18
17
|
from fastlib import ConfigManager
|
|
19
18
|
from fastlib.exception import BaseException
|
|
19
|
+
from fastlib.logging.handlers import logger
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
async def extract_request_data(request: Request) -> dict[str, Any]:
|
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
from loguru import logger
|
|
10
10
|
|
|
11
|
-
from fastlib.
|
|
11
|
+
from fastlib.config._log_config import LogConfig
|
|
12
12
|
from fastlib.config.manager import ConfigManager
|
|
13
13
|
|
|
14
14
|
|
|
@@ -42,7 +42,7 @@ class Logger:
|
|
|
42
42
|
def _get_config(cls) -> LogConfig:
|
|
43
43
|
"""Lazy load configuration."""
|
|
44
44
|
if cls._config is None:
|
|
45
|
-
cls._config = ConfigManager.
|
|
45
|
+
cls._config = ConfigManager.get_log_config()
|
|
46
46
|
return cls._config
|
|
47
47
|
|
|
48
48
|
@classmethod
|
|
@@ -6,13 +6,13 @@ from enum import Enum
|
|
|
6
6
|
|
|
7
7
|
from fastapi import Request
|
|
8
8
|
from jwt.exceptions import PyJWTError
|
|
9
|
-
from loguru import logger
|
|
10
9
|
from starlette.responses import JSONResponse
|
|
11
10
|
|
|
12
11
|
from fastlib import ConfigManager, security
|
|
13
12
|
from fastlib import constants as constant
|
|
14
13
|
from fastlib.contextvars import clear_current_user, set_current_user
|
|
15
14
|
from fastlib.enums import MediaTypeEnum
|
|
15
|
+
from fastlib.logging.handlers import logger
|
|
16
16
|
from fastlib.schema import UserCredential
|
|
17
17
|
|
|
18
18
|
# Load configuration
|
|
@@ -12,10 +12,11 @@ import traceback
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from fastapi import APIRouter
|
|
15
|
-
from loguru import logger
|
|
16
15
|
|
|
17
16
|
from fastlib.utils import str_util
|
|
18
17
|
|
|
18
|
+
from .logging.handlers import logger
|
|
19
|
+
|
|
19
20
|
|
|
20
21
|
def register_router(
|
|
21
22
|
controller_dirs: list[str] = None,
|
|
@@ -11,10 +11,10 @@ from collections.abc import AsyncIterable
|
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from typing import Any, Generic, TypeVar
|
|
13
13
|
|
|
14
|
-
from loguru import logger
|
|
15
14
|
from pydantic import BaseModel
|
|
16
15
|
|
|
17
16
|
from fastlib.cache.manager import get_cache_client
|
|
17
|
+
from fastlib.logging.handlers import logger
|
|
18
18
|
|
|
19
19
|
from .schema import (
|
|
20
20
|
BaseMessage,
|
|
@@ -18,13 +18,13 @@ from typing import (
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
import anyio
|
|
21
|
-
from loguru import logger
|
|
22
21
|
from starlette.background import BackgroundTask
|
|
23
22
|
from starlette.concurrency import iterate_in_threadpool
|
|
24
23
|
from starlette.datastructures import MutableHeaders
|
|
25
24
|
from starlette.responses import Response
|
|
26
25
|
from starlette.types import Message, Receive, Scope, Send
|
|
27
26
|
|
|
27
|
+
from fastlib.logging.handlers import logger
|
|
28
28
|
from fastlib.stream.event import ServerSentEvent, ensure_bytes
|
|
29
29
|
|
|
30
30
|
# Context variable for exit events per event loop
|
|
@@ -176,7 +176,6 @@ class EventSourceResponse(Response):
|
|
|
176
176
|
|
|
177
177
|
async for data in self.body_iterator:
|
|
178
178
|
chunk = ensure_bytes(data, self.sep)
|
|
179
|
-
logger.debug("chunk: %s", chunk)
|
|
180
179
|
with anyio.move_on_after(self.send_timeout) as cancel_scope:
|
|
181
180
|
await send(
|
|
182
181
|
{"type": "http.response.body", "body": chunk, "more_body": True}
|
|
@@ -233,7 +232,6 @@ class EventSourceResponse(Response):
|
|
|
233
232
|
)
|
|
234
233
|
)
|
|
235
234
|
ping_bytes = ensure_bytes(sse_ping, self.sep)
|
|
236
|
-
logger.debug("ping: %s", ping_bytes)
|
|
237
235
|
|
|
238
236
|
async with self._send_lock:
|
|
239
237
|
if self.active:
|
|
@@ -7,13 +7,14 @@ from contextlib import contextmanager
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
|
|
9
9
|
import pandas as pd
|
|
10
|
-
from loguru import logger
|
|
11
10
|
from openpyxl.styles import Alignment, Font, PatternFill
|
|
12
11
|
from openpyxl.utils import get_column_letter
|
|
13
12
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
14
13
|
from pydantic import BaseModel
|
|
15
14
|
from starlette.responses import StreamingResponse
|
|
16
15
|
|
|
16
|
+
from fastlib.logging.handlers import logger
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
class ExcelExporter:
|
|
19
20
|
"""Excel export handler for Pydantic models with customizable styling."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastlib-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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>
|
|
@@ -21,7 +21,6 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: aiosqlite>=0.21
|
|
23
23
|
Requires-Dist: alembic>=1.15.2
|
|
24
|
-
Requires-Dist: fakeredis>=2.32
|
|
25
24
|
Requires-Dist: fastapi[standard]>=0.115.12
|
|
26
25
|
Requires-Dist: loguru>=0.7.3
|
|
27
26
|
Requires-Dist: openpyxl>=3.1.5
|
|
@@ -6,7 +6,6 @@ fastlib/constants.py
|
|
|
6
6
|
fastlib/contextvars.py
|
|
7
7
|
fastlib/db_engine.py
|
|
8
8
|
fastlib/enums.py
|
|
9
|
-
fastlib/logging.py
|
|
10
9
|
fastlib/migrate.py
|
|
11
10
|
fastlib/openapi.py
|
|
12
11
|
fastlib/py.typed
|
|
@@ -15,18 +14,17 @@ fastlib/response.py
|
|
|
15
14
|
fastlib/router.py
|
|
16
15
|
fastlib/schema.py
|
|
17
16
|
fastlib/security.py
|
|
18
|
-
fastlib/_logging/__init__.py
|
|
19
|
-
fastlib/_logging/config.py
|
|
20
|
-
fastlib/_logging/handlers.py
|
|
21
17
|
fastlib/cache/__init__.py
|
|
22
18
|
fastlib/cache/_memony_cache.py
|
|
23
19
|
fastlib/cache/_redis_cache.py
|
|
24
20
|
fastlib/cache/manager.py
|
|
25
21
|
fastlib/config/__init__.py
|
|
26
22
|
fastlib/config/_database_config.py
|
|
23
|
+
fastlib/config/_log_config.py
|
|
27
24
|
fastlib/config/_security_config.py
|
|
28
25
|
fastlib/config/_server_config.py
|
|
29
26
|
fastlib/config/base.py
|
|
27
|
+
fastlib/config/decorator.py
|
|
30
28
|
fastlib/config/loader.py
|
|
31
29
|
fastlib/config/manager.py
|
|
32
30
|
fastlib/config/registry.py
|
|
@@ -40,6 +38,8 @@ fastlib/i18n/manager.py
|
|
|
40
38
|
fastlib/i18n/messages.py
|
|
41
39
|
fastlib/i18n/types.py
|
|
42
40
|
fastlib/i18n/utils.py
|
|
41
|
+
fastlib/logging/__init__.py
|
|
42
|
+
fastlib/logging/handlers.py
|
|
43
43
|
fastlib/mapper/__init__.py
|
|
44
44
|
fastlib/mapper/base_mapper.py
|
|
45
45
|
fastlib/mapper/impl/__init__.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastlib-py"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Fastlib, high performance, easy to use, fast to code, ready for production"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -31,7 +31,6 @@ classifiers = [
|
|
|
31
31
|
dependencies = [
|
|
32
32
|
"aiosqlite>=0.21",
|
|
33
33
|
"alembic>=1.15.2",
|
|
34
|
-
"fakeredis>=2.32",
|
|
35
34
|
"fastapi[standard]>=0.115.12",
|
|
36
35
|
"loguru>=0.7.3",
|
|
37
36
|
"openpyxl>=3.1.5",
|
|
@@ -118,6 +117,6 @@ title = "FastLib Coverage Report"
|
|
|
118
117
|
strict = true
|
|
119
118
|
|
|
120
119
|
[[tool.uv.index]]
|
|
121
|
-
name = "
|
|
122
|
-
url = "https://
|
|
120
|
+
name = "huaweicloud"
|
|
121
|
+
url = "https://repo.huaweicloud.com/repository/pypi/simple/"
|
|
123
122
|
default = true
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
"""
|
|
3
|
-
Log configuration and logging utility for the application.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Literal
|
|
9
|
-
|
|
10
|
-
from fastlib.config.base import BaseConfig
|
|
11
|
-
from fastlib.config.manager import config_class
|
|
12
|
-
from fastlib.config.utils import ProjectInfo
|
|
13
|
-
|
|
14
|
-
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@config_class("log")
|
|
18
|
-
@dataclass
|
|
19
|
-
class LogConfig(BaseConfig):
|
|
20
|
-
"""
|
|
21
|
-
Configuration for logging settings.
|
|
22
|
-
|
|
23
|
-
Attributes:
|
|
24
|
-
log_dir (str): Directory where log files will be stored.
|
|
25
|
-
app_name (str): Name of the application for log file naming.
|
|
26
|
-
log_level (LogLevel): Minimum log level to record. Possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL.
|
|
27
|
-
log_rotation (str): Frequency of log file rotation. Default is "1 day".
|
|
28
|
-
log_retention (str): How long to keep log files. Default is "90 days".
|
|
29
|
-
enable_console_log (bool): Whether to output logs to console. Default is True.
|
|
30
|
-
enable_json_logs (bool): Whether to use JSON format for logs. Default is False.
|
|
31
|
-
error_log_rotation (str): Frequency of error log rotation. Default is "1 day".
|
|
32
|
-
error_log_retention (str): How long to keep error log files. Default is "90 days".
|
|
33
|
-
enable_async (bool): Whether to use asynchronous logging. Default is True.
|
|
34
|
-
max_file_size (Optional[str]): Maximum size for log files before rotation. Default is "10 MB".
|
|
35
|
-
time_format (str): Format string for timestamps in log file names. Default is "YYYY-MM-DD".
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
log_dir: str = "/var/log"
|
|
39
|
-
app_name: str | None = None
|
|
40
|
-
log_level: LogLevel = "INFO"
|
|
41
|
-
log_rotation: str = "1 day"
|
|
42
|
-
log_retention: str = "90 days"
|
|
43
|
-
enable_console_log: bool = True
|
|
44
|
-
enable_json_logs: bool = False
|
|
45
|
-
error_log_rotation: str = "1 day"
|
|
46
|
-
error_log_retention: str = "90 days"
|
|
47
|
-
enable_async: bool = True
|
|
48
|
-
max_file_size: str | None = "10 MB"
|
|
49
|
-
time_format: str = "YYYY-MM-DD"
|
|
50
|
-
|
|
51
|
-
def __post_init__(self):
|
|
52
|
-
"""
|
|
53
|
-
Set default app_name if not provided.
|
|
54
|
-
|
|
55
|
-
If `app_name` is not provided, it will be set to the name of the project
|
|
56
|
-
retrieved from the `pyproject.toml` file.
|
|
57
|
-
"""
|
|
58
|
-
if self.app_name is None:
|
|
59
|
-
self.app_name = ProjectInfo.from_pyproject().name
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def default_format(self) -> str:
|
|
63
|
-
"""
|
|
64
|
-
Default log format string.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
str: The default format used for log messages.
|
|
68
|
-
"""
|
|
69
|
-
return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message}"
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def log_file_pattern(self) -> str:
|
|
73
|
-
"""
|
|
74
|
-
Returns the main log file pattern with timestamp.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
str: The pattern used to name the main log file.
|
|
78
|
-
"""
|
|
79
|
-
return f"{{time:{self.time_format}}}_{self.app_name}.log"
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def error_log_file_pattern(self) -> str:
|
|
83
|
-
"""
|
|
84
|
-
Returns the error log file pattern with timestamp.
|
|
85
|
-
|
|
86
|
-
Returns:
|
|
87
|
-
str: The pattern used to name the error log file.
|
|
88
|
-
"""
|
|
89
|
-
return f"{{time:{self.time_format}}}_error_{self.app_name}.log"
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def log_file_path(self) -> Path:
|
|
93
|
-
"""
|
|
94
|
-
Returns the full path for the main log file.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
Path: The full path to the main log file.
|
|
98
|
-
"""
|
|
99
|
-
return Path(self.log_dir) / self.log_file_pattern
|
|
100
|
-
|
|
101
|
-
@property
|
|
102
|
-
def error_log_file_path(self) -> Path:
|
|
103
|
-
"""
|
|
104
|
-
Returns the full path for the error log file.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
Path: The full path to the error log file.
|
|
108
|
-
"""
|
|
109
|
-
return Path(self.log_dir) / self.error_log_file_pattern
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
"""Memory (FakeRedis) Cache Manager for in-memory caching"""
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
|
|
6
|
-
from fakeredis.aioredis import FakeRedis
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class MemoryCacheManager:
|
|
10
|
-
"""Singleton manager for in-memory (FakeRedis) cache client."""
|
|
11
|
-
|
|
12
|
-
_instance: FakeRedis | None = None
|
|
13
|
-
_lock = asyncio.Lock()
|
|
14
|
-
|
|
15
|
-
@classmethod
|
|
16
|
-
async def get_instance(cls) -> FakeRedis:
|
|
17
|
-
"""Get a singleton FakeRedis instance."""
|
|
18
|
-
if cls._instance is None:
|
|
19
|
-
async with cls._lock:
|
|
20
|
-
if cls._instance is None:
|
|
21
|
-
cls._instance = FakeRedis()
|
|
22
|
-
return cls._instance
|
|
23
|
-
|
|
24
|
-
@classmethod
|
|
25
|
-
async def close(cls):
|
|
26
|
-
"""Close the FakeRedis instance (for cleanup in tests)."""
|
|
27
|
-
if cls._instance is not None:
|
|
28
|
-
await cls._instance.close()
|
|
29
|
-
cls._instance = None
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
async def reset(cls):
|
|
33
|
-
"""Clear all cached data without destroying the instance."""
|
|
34
|
-
if cls._instance is not None:
|
|
35
|
-
await cls._instance.flushall()
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
"""Redis cache implementation"""
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
from loguru import logger
|
|
8
|
-
|
|
9
|
-
from fastlib import ConfigManager
|
|
10
|
-
|
|
11
|
-
try:
|
|
12
|
-
import redis.asyncio as redis
|
|
13
|
-
except ModuleNotFoundError:
|
|
14
|
-
logger.warning(
|
|
15
|
-
"Cannot find redis module, please install it via `uv add redis[hiredis]`"
|
|
16
|
-
)
|
|
17
|
-
redis = None
|
|
18
|
-
RedisError = Exception
|
|
19
|
-
except Exception:
|
|
20
|
-
logger.error("Error importing redis module")
|
|
21
|
-
redis = None
|
|
22
|
-
RedisError = Exception
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class RedisCacheManager:
|
|
26
|
-
_instance: Optional["redis.Redis"] = None
|
|
27
|
-
_connection_pool: Optional["redis.ConnectionPool"] = None
|
|
28
|
-
_lock = asyncio.Lock()
|
|
29
|
-
|
|
30
|
-
@classmethod
|
|
31
|
-
async def get_instance(cls) -> "redis.Redis":
|
|
32
|
-
"""
|
|
33
|
-
Get redis instance
|
|
34
|
-
"""
|
|
35
|
-
if redis is None:
|
|
36
|
-
raise RuntimeError(
|
|
37
|
-
"Redis module is not available. Please install it via 'uv add redis[hiredis]'"
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
if cls._instance is None:
|
|
41
|
-
async with cls._lock:
|
|
42
|
-
if cls._connection_pool is None:
|
|
43
|
-
database_config = ConfigManager.get_database_config()
|
|
44
|
-
cls._connection_pool = redis.ConnectionPool.from_url(
|
|
45
|
-
f"redis://:{database_config.cache_pass}@{database_config.cache_host}:{database_config.cache_port}/{database_config.db_num}",
|
|
46
|
-
decode_responses=True,
|
|
47
|
-
)
|
|
48
|
-
if cls._instance is None:
|
|
49
|
-
cls._instance = redis.Redis(connection_pool=cls._connection_pool)
|
|
50
|
-
return cls._instance
|
|
51
|
-
|
|
52
|
-
@classmethod
|
|
53
|
-
async def close_pool(cls):
|
|
54
|
-
"""Close the connection pool."""
|
|
55
|
-
if cls._connection_pool:
|
|
56
|
-
await cls._connection_pool.disconnect()
|
|
57
|
-
cls._connection_pool = None
|
|
58
|
-
cls._instance = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|