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.
Files changed (81) hide show
  1. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/PKG-INFO +1 -2
  2. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/__init__.py +0 -2
  3. fastlib_py-0.4.0/fastlib/cache/_memony_cache.py +67 -0
  4. fastlib_py-0.4.0/fastlib/cache/_redis_cache.py +118 -0
  5. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/__init__.py +2 -1
  6. fastlib_py-0.4.0/fastlib/config/_log_config.py +102 -0
  7. fastlib_py-0.4.0/fastlib/config/decorator.py +25 -0
  8. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/loader.py +0 -4
  9. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/manager.py +15 -21
  10. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/utils.py +3 -5
  11. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/contextvars.py +1 -1
  12. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/exception_handler.py +1 -1
  13. fastlib_py-0.4.0/fastlib/logging/__init__.py +10 -0
  14. {fastlib_py-0.3.8/fastlib/_logging → fastlib_py-0.4.0/fastlib/logging}/handlers.py +2 -2
  15. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/jwt.py +1 -1
  16. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/migrate.py +1 -1
  17. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/router.py +2 -1
  18. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/handler.py +1 -1
  19. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/sse.py +1 -3
  20. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/excel_util.py +2 -1
  21. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/http_util.py +2 -1
  22. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/PKG-INFO +1 -2
  23. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/SOURCES.txt +4 -4
  24. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/requires.txt +0 -1
  25. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/pyproject.toml +3 -4
  26. fastlib_py-0.3.8/fastlib/_logging/config.py +0 -109
  27. fastlib_py-0.3.8/fastlib/cache/_memony_cache.py +0 -35
  28. fastlib_py-0.3.8/fastlib/cache/_redis_cache.py +0 -58
  29. fastlib_py-0.3.8/fastlib/logging.py +0 -8
  30. fastlib_py-0.3.8/fastlib/service/impl/__init__.py +0 -0
  31. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/LICENSE +0 -0
  32. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/README.md +0 -0
  33. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/cache/__init__.py +0 -0
  34. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/cache/manager.py +0 -0
  35. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_database_config.py +0 -0
  36. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_security_config.py +0 -0
  37. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/_server_config.py +0 -0
  38. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/base.py +0 -0
  39. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/config/registry.py +0 -0
  40. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/constants.py +0 -0
  41. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/db_engine.py +0 -0
  42. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/enums.py +0 -0
  43. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/__init__.py +0 -0
  44. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/base.py +0 -0
  45. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/exception/exception_manager.py +0 -0
  46. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/__init__.py +0 -0
  47. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/manager.py +0 -0
  48. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/messages.py +0 -0
  49. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/types.py +0 -0
  50. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/i18n/utils.py +0 -0
  51. {fastlib_py-0.3.8/fastlib/_logging → fastlib_py-0.4.0/fastlib/mapper}/__init__.py +0 -0
  52. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/mapper/base_mapper.py +0 -0
  53. {fastlib_py-0.3.8/fastlib/mapper → fastlib_py-0.4.0/fastlib/mapper/impl}/__init__.py +0 -0
  54. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/mapper/impl/base_mapper_impl.py +0 -0
  55. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/__init__.py +0 -0
  56. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/db_session.py +0 -0
  57. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/middleware/i18n.py +0 -0
  58. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/openapi.py +0 -0
  59. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/py.typed +0 -0
  60. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/request.py +0 -0
  61. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/response.py +0 -0
  62. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/schema.py +0 -0
  63. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/security.py +0 -0
  64. {fastlib_py-0.3.8/fastlib/mapper/impl → fastlib_py-0.4.0/fastlib/service}/__init__.py +0 -0
  65. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/service/base_service.py +0 -0
  66. {fastlib_py-0.3.8/fastlib/service → fastlib_py-0.4.0/fastlib/service/impl}/__init__.py +0 -0
  67. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/service/impl/base_service_impl.py +0 -0
  68. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/__init__.py +0 -0
  69. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/enums.py +0 -0
  70. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/event.py +0 -0
  71. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/stream/schema.py +0 -0
  72. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/__init__.py +0 -0
  73. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/file_util.py +0 -0
  74. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/ip_util.py +0 -0
  75. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/security_util.py +0 -0
  76. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/snowflake_util.py +0 -0
  77. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/str_util.py +0 -0
  78. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib/utils/validate_util.py +0 -0
  79. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/dependency_links.txt +0 -0
  80. {fastlib_py-0.3.8 → fastlib_py-0.4.0}/fastlib_py.egg-info/top_level.txt +0 -0
  81. {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.8
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
@@ -1,4 +1,5 @@
1
1
  from .base import BaseConfig
2
- from .manager import ConfigManager, config_class
2
+ from .decorator import config_class
3
+ from .manager import ConfigManager
3
4
 
4
5
  __all__ = ["BaseConfig", "ConfigManager", "config_class"]
@@ -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
- logger.error(
20
- "Cannot find tomllib module, please install it via `uv add tomllib` or `pip install tomllib`"
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
- logger.warning("Can not found alembic.ini in the project, use default db url")
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 loguru import logger
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]:
@@ -0,0 +1,10 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Export logging module"""
3
+
4
+ from .handlers import Logger
5
+
6
+ logger = Logger.initialize()
7
+
8
+ __all__ = [
9
+ "logger",
10
+ ]
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
 
9
9
  from loguru import logger
10
10
 
11
- from fastlib._logging.config import LogConfig
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.get_config_instance("log")
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
@@ -4,7 +4,7 @@
4
4
  import importlib
5
5
  from pathlib import Path
6
6
 
7
- from loguru import logger
7
+ from fastlib.logging.handlers import logger
8
8
 
9
9
  current_path = Path(__file__).resolve()
10
10
  src_parent = None
@@ -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."""
@@ -12,7 +12,8 @@ import uuid
12
12
  from typing import Any
13
13
 
14
14
  import httpx
15
- from loguru import logger
15
+
16
+ from fastlib.logging.handlers import logger
16
17
 
17
18
 
18
19
  class HTTPError(Exception):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastlib-py
3
- Version: 0.3.8
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,5 @@
1
1
  aiosqlite>=0.21
2
2
  alembic>=1.15.2
3
- fakeredis>=2.32
4
3
  fastapi[standard]>=0.115.12
5
4
  loguru>=0.7.3
6
5
  openpyxl>=3.1.5
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastlib-py"
3
- version = "0.3.8"
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 = "ustc"
122
- url = "https://pypi.mirrors.ustc.edu.cn/simple/"
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
@@ -1,8 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- """
3
- Logging module
4
- """
5
-
6
- from _logging.handlers import Logger
7
-
8
- logger = Logger.initialize()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes