fastlib-py 0.3.6__tar.gz → 0.3.8__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.6 → fastlib_py-0.3.8}/PKG-INFO +3 -4
- fastlib_py-0.3.8/fastlib/_logging/config.py +109 -0
- fastlib_py-0.3.8/fastlib/cache/_memony_cache.py +35 -0
- fastlib_py-0.3.8/fastlib/cache/_redis_cache.py +58 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/cache/manager.py +8 -7
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/logging.py +1 -1
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/router.py +0 -1
- {fastlib_py-0.3.6/fastlib/security → fastlib_py-0.3.8/fastlib}/security.py +124 -13
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/handler.py +3 -4
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib_py.egg-info/PKG-INFO +3 -4
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib_py.egg-info/SOURCES.txt +2 -4
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib_py.egg-info/requires.txt +2 -3
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/pyproject.toml +5 -6
- fastlib_py-0.3.6/fastlib/_logging/config.py +0 -102
- fastlib_py-0.3.6/fastlib/cache/_memony_cache.py +0 -267
- fastlib_py-0.3.6/fastlib/cache/_redis_cache.py +0 -370
- fastlib_py-0.3.6/fastlib/cache/base.py +0 -175
- fastlib_py-0.3.6/fastlib/security/__init__.py +0 -23
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/LICENSE +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/README.md +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/_logging/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/_logging/handlers.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/cache/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/_database_config.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/_security_config.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/_server_config.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/base.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/loader.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/manager.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/registry.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/config/utils.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/constants.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/contextvars.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/db_engine.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/enums.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/exception/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/exception/base.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/exception/exception_handler.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/exception/exception_manager.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/i18n/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/i18n/manager.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/i18n/messages.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/i18n/types.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/i18n/utils.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/mapper/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/mapper/base_mapper.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/mapper/impl/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/mapper/impl/base_mapper_impl.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/middleware/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/middleware/db_session.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/middleware/i18n.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/middleware/jwt.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/migrate.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/openapi.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/py.typed +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/request.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/response.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/schema.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/service/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/service/base_service.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/service/impl/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/service/impl/base_service_impl.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/enums.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/event.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/schema.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/stream/sse.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/__init__.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/excel_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/file_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/http_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/ip_util.py +0 -0
- /fastlib_py-0.3.6/fastlib/security/utils.py → /fastlib_py-0.3.8/fastlib/utils/security_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/snowflake_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/str_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib/utils/validate_util.py +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib_py.egg-info/dependency_links.txt +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/fastlib_py.egg-info/top_level.txt +0 -0
- {fastlib_py-0.3.6 → fastlib_py-0.3.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastlib-py
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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,13 +21,12 @@ 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:
|
|
24
|
+
Requires-Dist: fakeredis>=2.32
|
|
25
25
|
Requires-Dist: fastapi[standard]>=0.115.12
|
|
26
26
|
Requires-Dist: loguru>=0.7.3
|
|
27
27
|
Requires-Dist: openpyxl>=3.1.5
|
|
28
28
|
Requires-Dist: pandas>=2.2.3
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: pyjwt>=2.10.1
|
|
29
|
+
Requires-Dist: pyjwt[crypto]>=2.10.1
|
|
31
30
|
Requires-Dist: sqlmodel>=0.0.24
|
|
32
31
|
Dynamic: license-file
|
|
33
32
|
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
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()
|
|
@@ -0,0 +1,58 @@
|
|
|
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,23 +1,24 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
"""Cache Client manager to instantiate the appropriate cache client"""
|
|
3
3
|
|
|
4
|
-
from fastlib.cache.base import Cache
|
|
5
4
|
from fastlib.config.manager import ConfigManager
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
async def get_cache_client()
|
|
7
|
+
async def get_cache_client():
|
|
9
8
|
"""Initialize and return the appropriate cache client based on configuration.
|
|
10
9
|
|
|
11
10
|
Returns:
|
|
12
|
-
Cache: Redis client if Redis is enabled in config, otherwise returns
|
|
11
|
+
Cache: Redis client if Redis is enabled in config, otherwise returns FakeRedis client.
|
|
13
12
|
"""
|
|
14
13
|
config = ConfigManager.get_database_config()
|
|
14
|
+
|
|
15
15
|
if config.enable_redis:
|
|
16
|
-
from fastlib.cache._redis_cache import
|
|
16
|
+
from fastlib.cache._redis_cache import RedisManager
|
|
17
17
|
|
|
18
18
|
redis_client = await RedisManager.get_instance()
|
|
19
|
-
return
|
|
19
|
+
return redis_client
|
|
20
20
|
else:
|
|
21
|
-
from fastlib.cache._memony_cache import
|
|
21
|
+
from fastlib.cache._memony_cache import MemoryCacheManager
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
fake_client = await MemoryCacheManager.get_instance()
|
|
24
|
+
return fake_client
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
import secrets
|
|
2
5
|
from collections.abc import Callable
|
|
3
6
|
from datetime import datetime, timedelta
|
|
4
7
|
from typing import Any
|
|
@@ -7,7 +10,6 @@ import jwt
|
|
|
7
10
|
from fastapi import Depends, HTTPException, status
|
|
8
11
|
from fastapi.security import OAuth2PasswordBearer
|
|
9
12
|
from jwt.exceptions import InvalidTokenError
|
|
10
|
-
from passlib.context import CryptContext
|
|
11
13
|
|
|
12
14
|
from fastlib.config.manager import ConfigManager
|
|
13
15
|
from fastlib.schema import CurrentUser
|
|
@@ -16,8 +18,6 @@ from fastlib.schema import CurrentUser
|
|
|
16
18
|
security_config = ConfigManager.get_security_config()
|
|
17
19
|
server_config = ConfigManager.get_server_config()
|
|
18
20
|
|
|
19
|
-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
20
|
-
|
|
21
21
|
|
|
22
22
|
def decode_jwt_token(token: str) -> dict[str, Any]:
|
|
23
23
|
"""Decode and validate JWT token.
|
|
@@ -50,6 +50,11 @@ def decode_jwt_token(token: str) -> dict[str, Any]:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def get_oauth2_scheme() -> OAuth2PasswordBearer:
|
|
53
|
+
"""Create OAuth2 password bearer scheme.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
OAuth2PasswordBearer instance
|
|
57
|
+
"""
|
|
53
58
|
oauth2_scheme = OAuth2PasswordBearer(
|
|
54
59
|
tokenUrl=f"{server_config.api_prefix}/v1/auth/auth:signInWithEmailAndPassword"
|
|
55
60
|
)
|
|
@@ -57,8 +62,7 @@ def get_oauth2_scheme() -> OAuth2PasswordBearer:
|
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
def get_current_user() -> Callable[[], CurrentUser]:
|
|
60
|
-
"""
|
|
61
|
-
Acquire current info through access_token
|
|
65
|
+
"""Get current user from access token.
|
|
62
66
|
|
|
63
67
|
Returns:
|
|
64
68
|
CurrentUser instance
|
|
@@ -114,29 +118,86 @@ def create_token(
|
|
|
114
118
|
)
|
|
115
119
|
|
|
116
120
|
|
|
117
|
-
def
|
|
118
|
-
"""
|
|
121
|
+
def generate_salt(length: int = 16) -> str:
|
|
122
|
+
"""Generate a cryptographically secure random salt.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
length: Length of the salt in bytes
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Base64 encoded salt string
|
|
129
|
+
"""
|
|
130
|
+
return base64.b64encode(secrets.token_bytes(length)).decode("utf-8")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def hash_password_sha256(password: str, salt: str | None = None) -> tuple[str, str]:
|
|
134
|
+
"""Hash password using SHA-256 with salt.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
password: Plain text password to hash
|
|
138
|
+
salt: Salt for hashing. If None, generates random salt.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Tuple of (hashed_password, salt)
|
|
142
|
+
"""
|
|
143
|
+
if salt is None:
|
|
144
|
+
salt = generate_salt()
|
|
145
|
+
|
|
146
|
+
# Decode base64 salt
|
|
147
|
+
salt_bytes = base64.b64decode(salt.encode("utf-8"))
|
|
148
|
+
|
|
149
|
+
# Create hash with salt and password
|
|
150
|
+
hash_obj = hashlib.sha256()
|
|
151
|
+
hash_obj.update(salt_bytes + password.encode("utf-8"))
|
|
152
|
+
|
|
153
|
+
# Multiple iterations for increased security (optional)
|
|
154
|
+
for _ in range(1000): # 1000 iterations
|
|
155
|
+
hash_obj = hashlib.sha256(
|
|
156
|
+
hash_obj.digest() + salt_bytes + password.encode("utf-8")
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
hashed_password = base64.b64encode(hash_obj.digest()).decode("utf-8")
|
|
160
|
+
return hashed_password, salt
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def verify_password(
|
|
164
|
+
plain_password: str, hashed_password: str, salt: str | None = None
|
|
165
|
+
) -> bool:
|
|
166
|
+
"""Verify password against hashed version using SHA-256.
|
|
119
167
|
|
|
120
168
|
Args:
|
|
121
|
-
plain_password: Input password
|
|
169
|
+
plain_password: Input password to verify
|
|
122
170
|
hashed_password: Stored hashed password
|
|
171
|
+
salt: Salt used for the original hash. If None, uses a default salt.
|
|
123
172
|
|
|
124
173
|
Returns:
|
|
125
174
|
True if passwords match
|
|
126
175
|
"""
|
|
127
|
-
|
|
176
|
+
try:
|
|
177
|
+
# Use default salt if none provided (for backward compatibility)
|
|
178
|
+
if salt is None:
|
|
179
|
+
# Default salt - in production, this should be stored with the hash
|
|
180
|
+
salt = "default_salt_placeholder"
|
|
181
|
+
|
|
182
|
+
# Recalculate hash with the same salt and algorithm
|
|
183
|
+
calculated_hash, _ = hash_password_sha256(plain_password, salt)
|
|
184
|
+
|
|
185
|
+
# Use timing-safe comparison function
|
|
186
|
+
return secrets.compare_digest(calculated_hash, hashed_password)
|
|
187
|
+
except Exception:
|
|
188
|
+
return False
|
|
128
189
|
|
|
129
190
|
|
|
130
|
-
def get_password_hash(password: str) -> str:
|
|
131
|
-
"""Generate password hash.
|
|
191
|
+
def get_password_hash(password: str) -> tuple[str, str]:
|
|
192
|
+
"""Generate password hash using SHA-256.
|
|
132
193
|
|
|
133
194
|
Args:
|
|
134
195
|
password: Plain text password
|
|
135
196
|
|
|
136
197
|
Returns:
|
|
137
|
-
|
|
198
|
+
Tuple of (hashed_password, salt)
|
|
138
199
|
"""
|
|
139
|
-
return
|
|
200
|
+
return hash_password_sha256(password)
|
|
140
201
|
|
|
141
202
|
|
|
142
203
|
def validate_token(token: str) -> bool:
|
|
@@ -177,3 +238,53 @@ def get_user_id(token: str) -> int:
|
|
|
177
238
|
"""
|
|
178
239
|
payload = decode_jwt_token(token)
|
|
179
240
|
return int(payload["sub"])
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Backward compatibility wrapper functions
|
|
244
|
+
def verify_password_compat(plain_password: str, hashed_password: str) -> bool:
|
|
245
|
+
"""Compatibility version of password verification with default salt.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
plain_password: Input password to verify
|
|
249
|
+
hashed_password: Stored hashed password
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
True if passwords match
|
|
253
|
+
"""
|
|
254
|
+
return verify_password(plain_password, hashed_password, salt=None)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_password_hash_compat(password: str) -> str:
|
|
258
|
+
"""Compatibility version of password hash function.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
password: Plain text password
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Formatted string containing hash and salt (hash:salt)
|
|
265
|
+
"""
|
|
266
|
+
hashed_password, salt = get_password_hash(password)
|
|
267
|
+
return f"{hashed_password}:{salt}"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def parse_hashed_password(hashed_data: str) -> tuple[str, str]:
|
|
271
|
+
"""Parse hashed password data in format 'hash:salt'.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
hashed_data: String containing hash and salt separated by colon
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Tuple of (hashed_password, salt)
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
ValueError: If format is invalid
|
|
281
|
+
"""
|
|
282
|
+
if ":" not in hashed_data:
|
|
283
|
+
# If no salt is present, use default salt
|
|
284
|
+
return hashed_data, "default_salt_placeholder"
|
|
285
|
+
|
|
286
|
+
parts = hashed_data.split(":", 1)
|
|
287
|
+
if len(parts) != 2:
|
|
288
|
+
raise ValueError("Invalid hashed password format")
|
|
289
|
+
|
|
290
|
+
return parts[0], parts[1]
|
|
@@ -9,12 +9,11 @@ import asyncio
|
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
10
|
from collections.abc import AsyncIterable
|
|
11
11
|
from datetime import datetime
|
|
12
|
-
from typing import Generic, TypeVar
|
|
12
|
+
from typing import Any, Generic, TypeVar
|
|
13
13
|
|
|
14
14
|
from loguru import logger
|
|
15
15
|
from pydantic import BaseModel
|
|
16
16
|
|
|
17
|
-
from fastlib.cache.base import Cache
|
|
18
17
|
from fastlib.cache.manager import get_cache_client
|
|
19
18
|
|
|
20
19
|
from .schema import (
|
|
@@ -37,7 +36,7 @@ class StreamHandler(ABC, Generic[T]):
|
|
|
37
36
|
self,
|
|
38
37
|
message: T,
|
|
39
38
|
source: AsyncIterable[V],
|
|
40
|
-
storage:
|
|
39
|
+
storage: Any,
|
|
41
40
|
):
|
|
42
41
|
self._message = message
|
|
43
42
|
self._source = source
|
|
@@ -79,7 +78,7 @@ class AsyncStreamHandler(StreamHandler[T]):
|
|
|
79
78
|
self,
|
|
80
79
|
message: T,
|
|
81
80
|
source: AsyncIterable[V],
|
|
82
|
-
storage:
|
|
81
|
+
storage: Any,
|
|
83
82
|
buffer_size: int = 100,
|
|
84
83
|
):
|
|
85
84
|
super().__init__(message, source, storage)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastlib-py
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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,13 +21,12 @@ 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:
|
|
24
|
+
Requires-Dist: fakeredis>=2.32
|
|
25
25
|
Requires-Dist: fastapi[standard]>=0.115.12
|
|
26
26
|
Requires-Dist: loguru>=0.7.3
|
|
27
27
|
Requires-Dist: openpyxl>=3.1.5
|
|
28
28
|
Requires-Dist: pandas>=2.2.3
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: pyjwt>=2.10.1
|
|
29
|
+
Requires-Dist: pyjwt[crypto]>=2.10.1
|
|
31
30
|
Requires-Dist: sqlmodel>=0.0.24
|
|
32
31
|
Dynamic: license-file
|
|
33
32
|
|
|
@@ -14,13 +14,13 @@ fastlib/request.py
|
|
|
14
14
|
fastlib/response.py
|
|
15
15
|
fastlib/router.py
|
|
16
16
|
fastlib/schema.py
|
|
17
|
+
fastlib/security.py
|
|
17
18
|
fastlib/_logging/__init__.py
|
|
18
19
|
fastlib/_logging/config.py
|
|
19
20
|
fastlib/_logging/handlers.py
|
|
20
21
|
fastlib/cache/__init__.py
|
|
21
22
|
fastlib/cache/_memony_cache.py
|
|
22
23
|
fastlib/cache/_redis_cache.py
|
|
23
|
-
fastlib/cache/base.py
|
|
24
24
|
fastlib/cache/manager.py
|
|
25
25
|
fastlib/config/__init__.py
|
|
26
26
|
fastlib/config/_database_config.py
|
|
@@ -48,9 +48,6 @@ fastlib/middleware/__init__.py
|
|
|
48
48
|
fastlib/middleware/db_session.py
|
|
49
49
|
fastlib/middleware/i18n.py
|
|
50
50
|
fastlib/middleware/jwt.py
|
|
51
|
-
fastlib/security/__init__.py
|
|
52
|
-
fastlib/security/security.py
|
|
53
|
-
fastlib/security/utils.py
|
|
54
51
|
fastlib/service/__init__.py
|
|
55
52
|
fastlib/service/base_service.py
|
|
56
53
|
fastlib/service/impl/__init__.py
|
|
@@ -66,6 +63,7 @@ fastlib/utils/excel_util.py
|
|
|
66
63
|
fastlib/utils/file_util.py
|
|
67
64
|
fastlib/utils/http_util.py
|
|
68
65
|
fastlib/utils/ip_util.py
|
|
66
|
+
fastlib/utils/security_util.py
|
|
69
67
|
fastlib/utils/snowflake_util.py
|
|
70
68
|
fastlib/utils/str_util.py
|
|
71
69
|
fastlib/utils/validate_util.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastlib-py"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.8"
|
|
4
4
|
description = "Fastlib, high performance, easy to use, fast to code, ready for production"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -31,13 +31,12 @@ classifiers = [
|
|
|
31
31
|
dependencies = [
|
|
32
32
|
"aiosqlite>=0.21",
|
|
33
33
|
"alembic>=1.15.2",
|
|
34
|
-
"
|
|
34
|
+
"fakeredis>=2.32",
|
|
35
35
|
"fastapi[standard]>=0.115.12",
|
|
36
36
|
"loguru>=0.7.3",
|
|
37
37
|
"openpyxl>=3.1.5",
|
|
38
38
|
"pandas>=2.2.3",
|
|
39
|
-
"
|
|
40
|
-
"pyjwt>=2.10.1",
|
|
39
|
+
"pyjwt[crypto]>=2.10.1",
|
|
41
40
|
"sqlmodel>=0.0.24",
|
|
42
41
|
]
|
|
43
42
|
|
|
@@ -119,6 +118,6 @@ title = "FastLib Coverage Report"
|
|
|
119
118
|
strict = true
|
|
120
119
|
|
|
121
120
|
[[tool.uv.index]]
|
|
122
|
-
name = "
|
|
123
|
-
url = "https://pypi.
|
|
121
|
+
name = "ustc"
|
|
122
|
+
url = "https://pypi.mirrors.ustc.edu.cn/simple/"
|
|
124
123
|
default = true
|