aiteamutils 0.2.36__py3-none-any.whl → 0.2.38__py3-none-any.whl
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.
- aiteamutils/database.py +57 -15
- aiteamutils/dependencies.py +14 -3
- aiteamutils/security.py +7 -16
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.36.dist-info → aiteamutils-0.2.38.dist-info}/METADATA +1 -1
- {aiteamutils-0.2.36.dist-info → aiteamutils-0.2.38.dist-info}/RECORD +7 -7
- {aiteamutils-0.2.36.dist-info → aiteamutils-0.2.38.dist-info}/WHEEL +0 -0
aiteamutils/database.py
CHANGED
@@ -9,6 +9,7 @@ from sqlalchemy import or_
|
|
9
9
|
from fastapi import Request, Depends
|
10
10
|
from ulid import ULID
|
11
11
|
from sqlalchemy.sql import Select
|
12
|
+
import logging
|
12
13
|
|
13
14
|
from .exceptions import ErrorCode, CustomException
|
14
15
|
from .base_model import Base, BaseColumn
|
@@ -31,18 +32,33 @@ def get_database_service() -> 'DatabaseService':
|
|
31
32
|
if _database_service is None:
|
32
33
|
raise CustomException(
|
33
34
|
ErrorCode.DB_CONNECTION_ERROR,
|
34
|
-
detail="
|
35
|
+
detail="Database service is not initialized. Call init_database_service() first.",
|
35
36
|
source_function="get_database_service"
|
36
37
|
)
|
37
38
|
return _database_service
|
38
39
|
|
39
40
|
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
40
|
-
"""데이터베이스 세션을 생성하고 반환하는 비동기 제너레이터.
|
41
|
+
"""데이터베이스 세션을 생성하고 반환하는 비동기 제너레이터.
|
42
|
+
|
43
|
+
Yields:
|
44
|
+
AsyncSession: 데이터베이스 세션
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
CustomException: 세션 생성 실패 시
|
48
|
+
"""
|
41
49
|
db_service = get_database_service()
|
42
|
-
|
43
|
-
|
50
|
+
try:
|
51
|
+
async with db_service.get_session() as session:
|
44
52
|
yield session
|
45
|
-
|
53
|
+
except Exception as e:
|
54
|
+
raise CustomException(
|
55
|
+
ErrorCode.DB_CONNECTION_ERROR,
|
56
|
+
detail=f"Failed to get database session: {str(e)}",
|
57
|
+
source_function="get_db",
|
58
|
+
original_error=e
|
59
|
+
)
|
60
|
+
finally:
|
61
|
+
if 'session' in locals():
|
46
62
|
await session.close()
|
47
63
|
|
48
64
|
def get_database_session(db: AsyncSession = Depends(get_db)) -> 'DatabaseService':
|
@@ -921,14 +937,40 @@ def init_database_service(
|
|
921
937
|
|
922
938
|
Returns:
|
923
939
|
DatabaseService: 초기화된 데이터베이스 서비스 인스턴스
|
940
|
+
|
941
|
+
Raises:
|
942
|
+
CustomException: 데이터베이스 초기화 실패 시
|
924
943
|
"""
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
944
|
+
try:
|
945
|
+
global _database_service
|
946
|
+
if _database_service is not None:
|
947
|
+
logging.info("Database service already initialized")
|
948
|
+
return _database_service
|
949
|
+
|
950
|
+
logging.info(f"Initializing database service with URL: {db_url}")
|
951
|
+
_database_service = DatabaseService(
|
952
|
+
db_url=db_url,
|
953
|
+
db_echo=db_echo,
|
954
|
+
db_pool_size=db_pool_size,
|
955
|
+
db_max_overflow=db_max_overflow,
|
956
|
+
db_pool_timeout=db_pool_timeout,
|
957
|
+
db_pool_recycle=db_pool_recycle
|
958
|
+
)
|
959
|
+
|
960
|
+
if not _database_service.engine:
|
961
|
+
raise CustomException(
|
962
|
+
ErrorCode.DB_CONNECTION_ERROR,
|
963
|
+
detail="Database engine initialization failed",
|
964
|
+
source_function="init_database_service"
|
965
|
+
)
|
966
|
+
|
967
|
+
logging.info("Database service initialized successfully")
|
968
|
+
return _database_service
|
969
|
+
except Exception as e:
|
970
|
+
logging.error(f"Failed to initialize database service: {str(e)}")
|
971
|
+
raise CustomException(
|
972
|
+
ErrorCode.DB_CONNECTION_ERROR,
|
973
|
+
detail=f"Failed to initialize database service: {str(e)}",
|
974
|
+
source_function="init_database_service",
|
975
|
+
original_error=e
|
976
|
+
)
|
aiteamutils/dependencies.py
CHANGED
@@ -67,11 +67,22 @@ def get_service(name: str):
|
|
67
67
|
|
68
68
|
Returns:
|
69
69
|
Callable: 서비스 인스턴스를 반환하는 의존성 함수
|
70
|
+
|
71
|
+
Raises:
|
72
|
+
CustomException: 서비스 생성 실패 시
|
70
73
|
"""
|
71
74
|
def _get_service(db_service: DatabaseService = Depends(get_database_service)):
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
try:
|
76
|
+
repository_class, service_class = service_registry.get(name)
|
77
|
+
repository = repository_class(db_service)
|
78
|
+
return service_class(repository)
|
79
|
+
except Exception as e:
|
80
|
+
raise CustomException(
|
81
|
+
ErrorCode.SERVICE_NOT_REGISTERED,
|
82
|
+
detail=f"Failed to create service {name}: {str(e)}",
|
83
|
+
source_function="dependencies.get_service",
|
84
|
+
original_error=e
|
85
|
+
)
|
75
86
|
return _get_service
|
76
87
|
|
77
88
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/token")
|
aiteamutils/security.py
CHANGED
@@ -15,6 +15,9 @@ from .config import get_settings
|
|
15
15
|
|
16
16
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
17
17
|
|
18
|
+
# 전역 rate limit 상태 저장
|
19
|
+
_rate_limits: Dict[str, Dict[str, Any]] = {}
|
20
|
+
|
18
21
|
class RateLimitExceeded(CustomException):
|
19
22
|
"""Rate limit 초과 예외."""
|
20
23
|
|
@@ -200,14 +203,10 @@ def rate_limit(
|
|
200
203
|
key_func: Optional[Callable] = None
|
201
204
|
):
|
202
205
|
"""Rate limiting 데코레이터."""
|
203
|
-
rate_limits: Dict[str, Dict[str, Any]] = {}
|
204
|
-
|
205
206
|
def decorator(func: Callable) -> Callable:
|
206
207
|
@wraps(func)
|
207
208
|
async def wrapper(*args, **kwargs):
|
208
209
|
logging.info(f"[rate_limit] Starting rate limit check for {func.__name__}")
|
209
|
-
logging.info(f"[rate_limit] Args: {args}")
|
210
|
-
logging.info(f"[rate_limit] Kwargs: {kwargs}")
|
211
210
|
|
212
211
|
# Request 객체 찾기
|
213
212
|
request = None
|
@@ -240,16 +239,16 @@ def rate_limit(
|
|
240
239
|
now = datetime.now(UTC)
|
241
240
|
|
242
241
|
# 현재 rate limit 정보 가져오기
|
243
|
-
rate_info =
|
242
|
+
rate_info = _rate_limits.get(rate_limit_key)
|
244
243
|
logging.info(f"[rate_limit] Current rate info: {rate_info}")
|
245
244
|
|
246
245
|
if rate_info is None or (now - rate_info["start_time"]).total_seconds() >= window_seconds:
|
247
246
|
# 새로운 rate limit 설정
|
248
|
-
|
247
|
+
_rate_limits[rate_limit_key] = {
|
249
248
|
"count": 1,
|
250
249
|
"start_time": now
|
251
250
|
}
|
252
|
-
logging.info(f"[rate_limit] Created new rate limit: {
|
251
|
+
logging.info(f"[rate_limit] Created new rate limit: {_rate_limits[rate_limit_key]}")
|
253
252
|
else:
|
254
253
|
# 기존 rate limit 업데이트
|
255
254
|
if rate_info["count"] >= max_requests:
|
@@ -268,14 +267,6 @@ def rate_limit(
|
|
268
267
|
|
269
268
|
try:
|
270
269
|
logging.info(f"[rate_limit] Executing original function: {func.__name__}")
|
271
|
-
# db_service가 있는지 확인
|
272
|
-
for arg in args:
|
273
|
-
if hasattr(arg, 'db_service'):
|
274
|
-
logging.info(f"[rate_limit] Found db_service in args: {arg.db_service}")
|
275
|
-
for value in kwargs.values():
|
276
|
-
if hasattr(value, 'db_service'):
|
277
|
-
logging.info(f"[rate_limit] Found db_service in kwargs: {value.db_service}")
|
278
|
-
|
279
270
|
result = await func(*args, **kwargs)
|
280
271
|
logging.info("[rate_limit] Function executed successfully")
|
281
272
|
return result
|
@@ -290,7 +281,7 @@ def rate_limit(
|
|
290
281
|
source_function=func.__name__,
|
291
282
|
original_error=e
|
292
283
|
)
|
293
|
-
|
284
|
+
|
294
285
|
return wrapper
|
295
286
|
return decorator
|
296
287
|
|
aiteamutils/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
"""버전 정보"""
|
2
|
-
__version__ = "0.2.
|
2
|
+
__version__ = "0.2.38"
|
@@ -4,13 +4,13 @@ aiteamutils/base_repository.py,sha256=qdwQ7Sj2fUqxpDg6cWM48n_QbwPK_VUlG9zTSem8iC
|
|
4
4
|
aiteamutils/base_service.py,sha256=E4dHGE0DvhmRyFplh46SwKJOSF_nUL7OAsCkf_ZJF_8,24733
|
5
5
|
aiteamutils/cache.py,sha256=tr0Yn8VPYA9QHiKCUzciVlQ2J1RAwNo2K9lGMH4rY3s,1334
|
6
6
|
aiteamutils/config.py,sha256=kFKMeIx1KcuEwwx4VjZdCgoTOHCkG3ySYVJ0G6cvMoA,2849
|
7
|
-
aiteamutils/database.py,sha256=
|
8
|
-
aiteamutils/dependencies.py,sha256=
|
7
|
+
aiteamutils/database.py,sha256=ATFiwG_o6o1eeUa0VVQ_2E_ExC2Yg-Ui_eA62SL6vQk,35344
|
8
|
+
aiteamutils/dependencies.py,sha256=ShbGAWAEfZCe_8tfWW8D5zm7JA_OkcGEXX7sJr0xqfI,4605
|
9
9
|
aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
|
10
10
|
aiteamutils/exceptions.py,sha256=YV-ISya4wQlHk4twvGo16I5r8h22-tXpn9wa-b3WwDM,15231
|
11
|
-
aiteamutils/security.py,sha256=
|
11
|
+
aiteamutils/security.py,sha256=9gvEqDtE3RJaoCWqELPCjkg-IsSqZVrpMP6XPZaodWU,16024
|
12
12
|
aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
|
13
|
-
aiteamutils/version.py,sha256=
|
14
|
-
aiteamutils-0.2.
|
15
|
-
aiteamutils-0.2.
|
16
|
-
aiteamutils-0.2.
|
13
|
+
aiteamutils/version.py,sha256=ALyTk2taueqibsmGUnkLfV7gD0h49jnn_qqd8wQoe7M,42
|
14
|
+
aiteamutils-0.2.38.dist-info/METADATA,sha256=YuD1Vi-w0zGdm0tK3YdTBcpuBOVjdO1nlR3x1Onq45A,1718
|
15
|
+
aiteamutils-0.2.38.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
+
aiteamutils-0.2.38.dist-info/RECORD,,
|
File without changes
|