aiteamutils 0.2.59__py3-none-any.whl → 0.2.60__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/__init__.py +0 -5
- aiteamutils/base_repository.py +36 -190
- aiteamutils/base_service.py +34 -234
- aiteamutils/database.py +40 -199
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.59.dist-info → aiteamutils-0.2.60.dist-info}/METADATA +1 -1
- aiteamutils-0.2.60.dist-info/RECORD +15 -0
- aiteamutils/dependencies.py +0 -138
- aiteamutils-0.2.59.dist-info/RECORD +0 -16
- {aiteamutils-0.2.59.dist-info → aiteamutils-0.2.60.dist-info}/WHEEL +0 -0
aiteamutils/__init__.py
CHANGED
@@ -21,7 +21,6 @@ from .base_repository import BaseRepository
|
|
21
21
|
from .validators import validate_with
|
22
22
|
from .enums import ActivityType
|
23
23
|
from .version import __version__
|
24
|
-
from .dependencies import setup_dependencies, register_service
|
25
24
|
|
26
25
|
__all__ = [
|
27
26
|
# Base Models
|
@@ -32,10 +31,6 @@ __all__ = [
|
|
32
31
|
# Database
|
33
32
|
"DatabaseService",
|
34
33
|
|
35
|
-
# Dependencies
|
36
|
-
"setup_dependencies",
|
37
|
-
"register_service",
|
38
|
-
|
39
34
|
# Exceptions
|
40
35
|
"CustomException",
|
41
36
|
"ErrorCode",
|
aiteamutils/base_repository.py
CHANGED
@@ -1,208 +1,54 @@
|
|
1
|
-
|
2
|
-
from typing import TypeVar, Generic,
|
3
|
-
from sqlalchemy.orm import DeclarativeBase
|
4
|
-
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
5
|
-
from sqlalchemy import select, or_, and_
|
6
|
-
from .exceptions import CustomException, ErrorCode
|
7
|
-
from sqlalchemy.orm import joinedload
|
8
|
-
from sqlalchemy.sql import Select
|
9
|
-
from fastapi import Request
|
1
|
+
#기본 라이브러리
|
2
|
+
from typing import TypeVar, Generic, Type, Any, Dict, List, Optional
|
3
|
+
from sqlalchemy.orm import DeclarativeBase
|
10
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
+
from sqlalchemy.exc import SQLAlchemyError
|
6
|
+
from sqlalchemy import select
|
7
|
+
|
8
|
+
#패키지 라이브러리
|
9
|
+
from .exceptions import ErrorCode, CustomException
|
10
|
+
from .database import list_entities
|
11
11
|
|
12
12
|
ModelType = TypeVar("ModelType", bound=DeclarativeBase)
|
13
13
|
|
14
14
|
class BaseRepository(Generic[ModelType]):
|
15
|
-
##################
|
16
|
-
# 1. 초기화 영역 #
|
17
|
-
##################
|
18
15
|
def __init__(self, session: AsyncSession, model: Type[ModelType]):
|
19
|
-
|
20
|
-
Args:
|
21
|
-
session (AsyncSession): 데이터베이스 세션
|
22
|
-
model (Type[ModelType]): 모델 클래스
|
23
|
-
"""
|
24
|
-
self.session = session
|
25
|
-
self.model = model
|
26
|
-
|
27
|
-
@property
|
28
|
-
def session(self):
|
29
|
-
"""현재 세션을 반환합니다."""
|
30
|
-
if self._session is None:
|
16
|
+
if session is None:
|
31
17
|
raise CustomException(
|
32
18
|
ErrorCode.DB_CONNECTION_ERROR,
|
33
19
|
detail="Database session is not set",
|
34
20
|
source_function=f"{self.__class__.__name__}.session"
|
35
21
|
)
|
36
|
-
|
37
|
-
|
38
|
-
@session.setter
|
39
|
-
def session(self, value):
|
40
|
-
"""세션을 설정합니다."""
|
41
|
-
self._session = value
|
42
|
-
|
43
|
-
#######################
|
44
|
-
# 2. CRUD 작업 #
|
45
|
-
#######################
|
46
|
-
async def get(
|
47
|
-
self,
|
48
|
-
ulid: str
|
49
|
-
) -> Optional[Dict[str, Any]]:
|
50
|
-
"""ULID로 엔티티를 조회합니다."""
|
51
|
-
try:
|
52
|
-
stmt = select(self.model).filter_by(ulid=ulid, is_deleted=False)
|
53
|
-
result = await self.session.execute(stmt)
|
54
|
-
entity = result.scalars().unique().first()
|
55
|
-
|
56
|
-
if not entity:
|
57
|
-
raise CustomException(
|
58
|
-
ErrorCode.DB_NO_RESULT,
|
59
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
60
|
-
source_function=f"{self.__class__.__name__}.get"
|
61
|
-
)
|
62
|
-
return entity
|
63
|
-
except CustomException as e:
|
64
|
-
e.detail = f"Repository error for {self.model.__tablename__}: {e.detail}"
|
65
|
-
e.source_function = f"{self.__class__.__name__}.get -> {e.source_function}"
|
66
|
-
raise e
|
67
|
-
except SQLAlchemyError as e:
|
22
|
+
if model is None:
|
68
23
|
raise CustomException(
|
69
|
-
ErrorCode.
|
70
|
-
detail=
|
71
|
-
source_function=f"{self.__class__.__name__}.
|
72
|
-
original_error=e
|
24
|
+
ErrorCode.DB_CONNECTION_ERROR,
|
25
|
+
detail="Model is not set",
|
26
|
+
source_function=f"{self.__class__.__name__}.model"
|
73
27
|
)
|
74
28
|
|
29
|
+
self.session = session
|
30
|
+
self.model = model
|
31
|
+
|
32
|
+
@property
|
33
|
+
def session(self) -> AsyncSession:
|
34
|
+
return self._session
|
35
|
+
|
75
36
|
async def list(
|
76
37
|
self,
|
77
38
|
skip: int = 0,
|
78
39
|
limit: int = 100,
|
79
|
-
filters: Dict[str, Any]
|
80
|
-
|
81
|
-
) -> List[
|
82
|
-
"""
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# 페이지네이션 적용
|
95
|
-
stmt = stmt.limit(limit).offset(skip)
|
96
|
-
|
97
|
-
result = await self.session.execute(stmt)
|
98
|
-
return result.scalars().unique().all()
|
99
|
-
|
100
|
-
except SQLAlchemyError as e:
|
101
|
-
raise CustomException(
|
102
|
-
ErrorCode.DB_QUERY_ERROR,
|
103
|
-
detail=f"Unexpected repository list error in {self.model.__tablename__}: {str(e)}",
|
104
|
-
source_function=f"{self.__class__.__name__}.list",
|
105
|
-
original_error=e,
|
106
|
-
)
|
107
|
-
|
108
|
-
async def create(self, data: Dict[str, Any]) -> ModelType:
|
109
|
-
"""새로운 엔티티를 생성합니다."""
|
110
|
-
try:
|
111
|
-
entity = self.model(**data)
|
112
|
-
self.session.add(entity)
|
113
|
-
await self.session.flush()
|
114
|
-
await self.session.refresh(entity)
|
115
|
-
return entity
|
116
|
-
except IntegrityError as e:
|
117
|
-
await self.session.rollback()
|
118
|
-
self._handle_integrity_error(e, "create", data)
|
119
|
-
except SQLAlchemyError as e:
|
120
|
-
await self.session.rollback()
|
121
|
-
raise CustomException(
|
122
|
-
ErrorCode.DB_CREATE_ERROR,
|
123
|
-
detail=f"Database create error in {self.model.__tablename__}: {str(e)}",
|
124
|
-
source_function=f"{self.__class__.__name__}.create",
|
125
|
-
original_error=e
|
126
|
-
)
|
127
|
-
|
128
|
-
async def update(self, ulid: str, data: Dict[str, Any]) -> Optional[ModelType]:
|
129
|
-
"""기존 엔티티를 수정합니다."""
|
130
|
-
try:
|
131
|
-
stmt = select(self.model).filter_by(ulid=ulid, is_deleted=False)
|
132
|
-
result = await self.session.execute(stmt)
|
133
|
-
entity = result.scalars().first()
|
134
|
-
|
135
|
-
if not entity:
|
136
|
-
raise CustomException(
|
137
|
-
ErrorCode.DB_NO_RESULT,
|
138
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
139
|
-
source_function=f"{self.__class__.__name__}.update"
|
140
|
-
)
|
141
|
-
|
142
|
-
for key, value in data.items():
|
143
|
-
setattr(entity, key, value)
|
144
|
-
|
145
|
-
await self.session.flush()
|
146
|
-
await self.session.refresh(entity)
|
147
|
-
return entity
|
148
|
-
|
149
|
-
except IntegrityError as e:
|
150
|
-
await self.session.rollback()
|
151
|
-
self._handle_integrity_error(e, "update", data)
|
152
|
-
except SQLAlchemyError as e:
|
153
|
-
await self.session.rollback()
|
154
|
-
raise CustomException(
|
155
|
-
ErrorCode.DB_UPDATE_ERROR,
|
156
|
-
detail=f"Database update error in {self.model.__tablename__}: {str(e)}",
|
157
|
-
source_function=f"{self.__class__.__name__}.update",
|
158
|
-
original_error=e
|
159
|
-
)
|
160
|
-
|
161
|
-
async def delete(self, ulid: str) -> bool:
|
162
|
-
"""엔티티를 소프트 삭제합니다."""
|
163
|
-
try:
|
164
|
-
stmt = select(self.model).filter_by(ulid=ulid, is_deleted=False)
|
165
|
-
result = await self.session.execute(stmt)
|
166
|
-
entity = result.scalars().first()
|
167
|
-
|
168
|
-
if not entity:
|
169
|
-
raise CustomException(
|
170
|
-
ErrorCode.DB_NO_RESULT,
|
171
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
172
|
-
source_function=f"{self.__class__.__name__}.delete"
|
173
|
-
)
|
174
|
-
|
175
|
-
entity.is_deleted = True
|
176
|
-
await self.session.flush()
|
177
|
-
return True
|
178
|
-
|
179
|
-
except SQLAlchemyError as e:
|
180
|
-
await self.session.rollback()
|
181
|
-
raise CustomException(
|
182
|
-
ErrorCode.DB_DELETE_ERROR,
|
183
|
-
detail=f"Database delete error in {self.model.__tablename__}: {str(e)}",
|
184
|
-
source_function=f"{self.__class__.__name__}.delete",
|
185
|
-
original_error=e
|
186
|
-
)
|
187
|
-
|
188
|
-
async def real_delete(self, ulid: str) -> bool:
|
189
|
-
"""엔티티를 실제로 삭제합니다."""
|
190
|
-
try:
|
191
|
-
stmt = select(self.model).filter_by(ulid=ulid)
|
192
|
-
result = await self.session.execute(stmt)
|
193
|
-
entity = result.scalars().first()
|
194
|
-
|
195
|
-
if entity:
|
196
|
-
await self.session.delete(entity)
|
197
|
-
await self.session.flush()
|
198
|
-
return True
|
199
|
-
return False
|
200
|
-
|
201
|
-
except SQLAlchemyError as e:
|
202
|
-
await self.session.rollback()
|
203
|
-
raise CustomException(
|
204
|
-
ErrorCode.DB_DELETE_ERROR,
|
205
|
-
detail=f"Database real delete error in {self.model.__tablename__}: {str(e)}",
|
206
|
-
source_function=f"{self.__class__.__name__}.real_delete",
|
207
|
-
original_error=e
|
208
|
-
)
|
40
|
+
filters: Optional[Dict[str, Any]] = None,
|
41
|
+
joins: Optional[List[Any]] = None
|
42
|
+
) -> List[ModelType]:
|
43
|
+
"""
|
44
|
+
엔티티 목록 조회.
|
45
|
+
"""
|
46
|
+
# 기본 CRUD 작업 호출
|
47
|
+
return await list_entities(
|
48
|
+
session=self.db_session,
|
49
|
+
model=self.model,
|
50
|
+
skip=skip,
|
51
|
+
limit=limit,
|
52
|
+
filters=filters,
|
53
|
+
joins=joins,
|
54
|
+
)
|
aiteamutils/base_service.py
CHANGED
@@ -1,260 +1,60 @@
|
|
1
|
-
|
2
|
-
from datetime import datetime
|
3
|
-
from typing import TypeVar, Generic, Dict, Any, List, Optional, Type
|
4
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
-
from sqlalchemy.orm import DeclarativeBase
|
1
|
+
#기본 라이브러리
|
6
2
|
from fastapi import Request
|
7
|
-
|
8
|
-
from .
|
9
|
-
from .
|
3
|
+
from typing import TypeVar, Generic, Type, Dict, Any, Union, List
|
4
|
+
from sqlalchemy.orm import DeclarativeBase
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
+
from datetime import datetime
|
7
|
+
#패키지 라이브러리
|
8
|
+
from .exceptions import ErrorCode, CustomException
|
10
9
|
from .base_repository import BaseRepository
|
11
10
|
|
12
11
|
ModelType = TypeVar("ModelType", bound=DeclarativeBase)
|
13
12
|
|
14
13
|
class BaseService(Generic[ModelType]):
|
14
|
+
##################
|
15
|
+
# 1. 초기화 영역 #
|
16
|
+
##################
|
15
17
|
def __init__(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
self,
|
19
|
+
model: Type[ModelType],
|
20
|
+
repository: BaseRepository[ModelType],
|
21
|
+
db_session: AsyncSession
|
20
22
|
):
|
21
|
-
|
22
|
-
|
23
|
-
Args:
|
24
|
-
db: 데이터베이스 서비스
|
25
|
-
repository: 레포지토리 인스턴스 (선택)
|
26
|
-
request: FastAPI 요청 객체 (선택)
|
27
|
-
"""
|
28
|
-
self.db = db
|
23
|
+
self.model = model
|
29
24
|
self.repository = repository
|
30
|
-
self.
|
31
|
-
self.model = repository.model if repository else None
|
32
|
-
|
33
|
-
def _process_response(self, entity: ModelType, response_model: Any = None) -> Dict[str, Any]:
|
34
|
-
"""응답 데이터를 처리합니다.
|
35
|
-
|
36
|
-
Args:
|
37
|
-
entity: 처리할 엔티티
|
38
|
-
response_model: 응답 모델 클래스
|
39
|
-
|
40
|
-
Returns:
|
41
|
-
처리된 데이터
|
42
|
-
"""
|
43
|
-
if not entity:
|
44
|
-
return None
|
45
|
-
|
46
|
-
# 기본 데이터 변환
|
47
|
-
result = {}
|
48
|
-
|
49
|
-
# 테이블 컬럼 처리
|
50
|
-
for column in entity.__table__.columns:
|
51
|
-
value = getattr(entity, column.name)
|
52
|
-
if isinstance(value, datetime):
|
53
|
-
value = value.isoformat()
|
54
|
-
result[column.name] = value
|
55
|
-
|
56
|
-
# Relationship 처리 (이미 로드된 관계만)
|
57
|
-
for relationship in entity.__mapper__.relationships:
|
58
|
-
if relationship.key not in entity.__dict__:
|
59
|
-
continue
|
60
|
-
|
61
|
-
try:
|
62
|
-
value = getattr(entity, relationship.key)
|
63
|
-
if value is not None:
|
64
|
-
if isinstance(value, list):
|
65
|
-
result[relationship.key] = [
|
66
|
-
self._process_response(item)
|
67
|
-
for item in value
|
68
|
-
]
|
69
|
-
else:
|
70
|
-
result[relationship.key] = self._process_response(value)
|
71
|
-
else:
|
72
|
-
result[relationship.key] = None
|
73
|
-
except Exception:
|
74
|
-
result[relationship.key] = None
|
75
|
-
|
76
|
-
# response_model이 있는 경우 필터링
|
77
|
-
if response_model:
|
78
|
-
# response_model에 없는 필드 제거
|
79
|
-
keys_to_remove = [key for key in result if key not in response_model.model_fields]
|
80
|
-
for key in keys_to_remove:
|
81
|
-
result.pop(key)
|
82
|
-
# 모델 검증
|
83
|
-
return response_model(**result).model_dump()
|
84
|
-
|
85
|
-
return result
|
86
|
-
|
87
|
-
async def create(
|
88
|
-
self,
|
89
|
-
data: Dict[str, Any],
|
90
|
-
response_model: Any = None
|
91
|
-
) -> Dict[str, Any]:
|
92
|
-
"""엔티티를 생성합니다.
|
93
|
-
|
94
|
-
Args:
|
95
|
-
data: 생성할 데이터
|
96
|
-
response_model: 응답 모델 클래스
|
97
|
-
|
98
|
-
Returns:
|
99
|
-
생성된 엔티티
|
100
|
-
|
101
|
-
Raises:
|
102
|
-
CustomException: 생성 실패 시
|
103
|
-
"""
|
104
|
-
try:
|
105
|
-
entity = await self.db.create_entity(self.model, data)
|
106
|
-
return self._process_response(entity, response_model)
|
107
|
-
except CustomException as e:
|
108
|
-
raise e
|
109
|
-
except Exception as e:
|
110
|
-
raise CustomException(
|
111
|
-
ErrorCode.DB_CREATE_ERROR,
|
112
|
-
detail=str(e),
|
113
|
-
source_function=f"{self.__class__.__name__}.create",
|
114
|
-
original_error=e
|
115
|
-
)
|
116
|
-
|
117
|
-
async def get(
|
118
|
-
self,
|
119
|
-
ulid: str,
|
120
|
-
response_model: Any = None
|
121
|
-
) -> Dict[str, Any]:
|
122
|
-
"""엔티티를 조회합니다.
|
123
|
-
|
124
|
-
Args:
|
125
|
-
ulid: 조회할 엔티티의 ULID
|
126
|
-
response_model: 응답 모델 클래스
|
127
|
-
|
128
|
-
Returns:
|
129
|
-
조회된 엔티티
|
130
|
-
|
131
|
-
Raises:
|
132
|
-
CustomException: 조회 실패 시
|
133
|
-
"""
|
134
|
-
try:
|
135
|
-
entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
|
136
|
-
if not entity:
|
137
|
-
raise CustomException(
|
138
|
-
ErrorCode.NOT_FOUND,
|
139
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
140
|
-
source_function=f"{self.__class__.__name__}.get"
|
141
|
-
)
|
142
|
-
return self._process_response(entity, response_model)
|
143
|
-
except CustomException as e:
|
144
|
-
raise e
|
145
|
-
except Exception as e:
|
146
|
-
raise CustomException(
|
147
|
-
ErrorCode.DB_QUERY_ERROR,
|
148
|
-
detail=str(e),
|
149
|
-
source_function=f"{self.__class__.__name__}.get",
|
150
|
-
original_error=e
|
151
|
-
)
|
25
|
+
self.db_session = db_session
|
152
26
|
|
153
27
|
async def list(
|
154
28
|
self,
|
155
29
|
skip: int = 0,
|
156
30
|
limit: int = 100,
|
157
|
-
filters:
|
158
|
-
|
31
|
+
filters: Dict[str, Any] | None = None,
|
32
|
+
search_params: Dict[str, Any] | None = None,
|
33
|
+
model_name: str | None = None,
|
159
34
|
) -> List[Dict[str, Any]]:
|
160
|
-
"""엔티티 목록을 조회합니다.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
skip: 건너뛸 레코드 수
|
164
|
-
limit: 조회할 최대 레코드 수
|
165
|
-
filters: 필터 조건
|
166
|
-
response_model: 응답 모델 클래스
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
엔티티 목록
|
170
|
-
|
171
|
-
Raises:
|
172
|
-
CustomException: 조회 실패 시
|
173
|
-
"""
|
174
35
|
try:
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
36
|
+
# 모델 이름을 통한 동적 처리
|
37
|
+
if model_name:
|
38
|
+
if model_name not in self.additional_models:
|
39
|
+
raise CustomException(
|
40
|
+
ErrorCode.INVALID_REQUEST,
|
41
|
+
detail=f"Model {model_name} not registered",
|
42
|
+
source_function=f"{self.__class__.__name__}.list"
|
43
|
+
)
|
44
|
+
model = self.additional_models[model_name]
|
45
|
+
return await self.repository.list(skip=skip, limit=limit, filters=filters, model=model)
|
46
|
+
|
47
|
+
return await self.repository.list(skip=skip, limit=limit, filters=filters)
|
182
48
|
except CustomException as e:
|
49
|
+
e.detail = f"Service list error for {self.repository.model.__tablename__}: {e.detail}"
|
50
|
+
e.source_function = f"{self.__class__.__name__}.list -> {e.source_function}"
|
183
51
|
raise e
|
184
52
|
except Exception as e:
|
185
53
|
raise CustomException(
|
186
|
-
ErrorCode.
|
54
|
+
ErrorCode.INTERNAL_ERROR,
|
187
55
|
detail=str(e),
|
188
56
|
source_function=f"{self.__class__.__name__}.list",
|
189
57
|
original_error=e
|
190
58
|
)
|
191
59
|
|
192
|
-
async def update(
|
193
|
-
self,
|
194
|
-
ulid: str,
|
195
|
-
data: Dict[str, Any],
|
196
|
-
response_model: Any = None
|
197
|
-
) -> Dict[str, Any]:
|
198
|
-
"""엔티티를 수정합니다.
|
199
|
-
|
200
|
-
Args:
|
201
|
-
ulid: 수정할 엔티티의 ULID
|
202
|
-
data: 수정할 데이터
|
203
|
-
response_model: 응답 모델 클래스
|
204
|
-
|
205
|
-
Returns:
|
206
|
-
수정된 엔티티
|
207
|
-
|
208
|
-
Raises:
|
209
|
-
CustomException: 수정 실패 시
|
210
|
-
"""
|
211
|
-
try:
|
212
|
-
entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
|
213
|
-
if not entity:
|
214
|
-
raise CustomException(
|
215
|
-
ErrorCode.NOT_FOUND,
|
216
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
217
|
-
source_function=f"{self.__class__.__name__}.update"
|
218
|
-
)
|
219
|
-
updated = await self.db.update_entity(entity, data)
|
220
|
-
return self._process_response(updated, response_model)
|
221
|
-
except CustomException as e:
|
222
|
-
raise e
|
223
|
-
except Exception as e:
|
224
|
-
raise CustomException(
|
225
|
-
ErrorCode.DB_UPDATE_ERROR,
|
226
|
-
detail=str(e),
|
227
|
-
source_function=f"{self.__class__.__name__}.update",
|
228
|
-
original_error=e
|
229
|
-
)
|
230
60
|
|
231
|
-
async def delete(self, ulid: str) -> bool:
|
232
|
-
"""엔티티를 삭제합니다.
|
233
|
-
|
234
|
-
Args:
|
235
|
-
ulid: 삭제할 엔티티의 ULID
|
236
|
-
|
237
|
-
Returns:
|
238
|
-
삭제 성공 여부
|
239
|
-
|
240
|
-
Raises:
|
241
|
-
CustomException: 삭제 실패 시
|
242
|
-
"""
|
243
|
-
try:
|
244
|
-
entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
|
245
|
-
if not entity:
|
246
|
-
raise CustomException(
|
247
|
-
ErrorCode.NOT_FOUND,
|
248
|
-
detail=f"{self.model.__tablename__}|ulid|{ulid}",
|
249
|
-
source_function=f"{self.__class__.__name__}.delete"
|
250
|
-
)
|
251
|
-
return await self.db.delete_entity(entity)
|
252
|
-
except CustomException as e:
|
253
|
-
raise e
|
254
|
-
except Exception as e:
|
255
|
-
raise CustomException(
|
256
|
-
ErrorCode.DB_DELETE_ERROR,
|
257
|
-
detail=str(e),
|
258
|
-
source_function=f"{self.__class__.__name__}.delete",
|
259
|
-
original_error=e
|
260
|
-
)
|
aiteamutils/database.py
CHANGED
@@ -1,207 +1,48 @@
|
|
1
|
-
|
2
|
-
from typing import
|
3
|
-
from sqlalchemy import select, and_
|
1
|
+
#기본 라이브러리
|
2
|
+
from typing import TypeVar, Generic, Type, Any, Dict, List, Optional
|
4
3
|
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
-
from sqlalchemy
|
4
|
+
from sqlalchemy import select, and_
|
5
|
+
from sqlalchemy.orm import DeclarativeBase
|
6
|
+
from sqlalchemy.exc import SQLAlchemyError
|
6
7
|
|
7
|
-
|
8
|
-
from .base_model import Base
|
8
|
+
ModelType = TypeVar("ModelType", bound=DeclarativeBase)
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
"""DatabaseService 초기화
|
13
|
-
|
14
|
-
Args:
|
15
|
-
session (AsyncSession): 외부에서 주입받은 데이터베이스 세션
|
16
|
-
"""
|
17
|
-
self._session = session
|
18
|
-
|
19
|
-
@property
|
20
|
-
def session(self) -> AsyncSession:
|
21
|
-
"""현재 세션을 반환합니다."""
|
22
|
-
if self._session is None:
|
23
|
-
raise CustomException(
|
24
|
-
ErrorCode.DB_CONNECTION_ERROR,
|
25
|
-
detail="session",
|
26
|
-
source_function="DatabaseService.session"
|
27
|
-
)
|
28
|
-
return self._session
|
10
|
+
#패키지 라이브러리
|
11
|
+
from .exceptions import ErrorCode, CustomException
|
29
12
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Raises:
|
45
|
-
CustomException: 엔티티 생성 실패 시
|
46
|
-
"""
|
47
|
-
try:
|
48
|
-
entity = model(**entity_data)
|
49
|
-
self.session.add(entity)
|
50
|
-
await self.session.flush()
|
51
|
-
await self.session.refresh(entity)
|
52
|
-
return entity
|
53
|
-
except IntegrityError as e:
|
54
|
-
await self.session.rollback()
|
55
|
-
raise CustomException(
|
56
|
-
ErrorCode.DB_INTEGRITY_ERROR,
|
57
|
-
detail=str(e),
|
58
|
-
source_function="DatabaseService.create_entity",
|
59
|
-
original_error=e
|
60
|
-
)
|
61
|
-
except Exception as e:
|
62
|
-
await self.session.rollback()
|
63
|
-
raise CustomException(
|
64
|
-
ErrorCode.DB_CREATE_ERROR,
|
65
|
-
detail=str(e),
|
66
|
-
source_function="DatabaseService.create_entity",
|
67
|
-
original_error=e
|
68
|
-
)
|
13
|
+
##################
|
14
|
+
# 1. 쿼리 실행 #
|
15
|
+
##################
|
16
|
+
async def list_entities(
|
17
|
+
session: AsyncSession,
|
18
|
+
model: Type[ModelType],
|
19
|
+
skip: int = 0,
|
20
|
+
limit: int = 100,
|
21
|
+
filters: Optional[Dict[str, Any]] = None,
|
22
|
+
joins: Optional[List[Any]] = None
|
23
|
+
) -> List[Dict[str, Any]]:
|
24
|
+
try:
|
25
|
+
query = select(model)
|
69
26
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
) -> Optional[Any]:
|
75
|
-
"""필터 조건으로 엔티티를 조회합니다.
|
76
|
-
|
77
|
-
Args:
|
78
|
-
model: 모델 클래스
|
79
|
-
filters: 필터 조건
|
80
|
-
|
81
|
-
Returns:
|
82
|
-
조회된 엔티티 또는 None
|
83
|
-
|
84
|
-
Raises:
|
85
|
-
CustomException: 조회 실패 시
|
86
|
-
"""
|
87
|
-
try:
|
88
|
-
stmt = select(model).filter_by(**filters)
|
89
|
-
result = await self.session.execute(stmt)
|
90
|
-
return result.scalars().first()
|
91
|
-
except Exception as e:
|
92
|
-
raise CustomException(
|
93
|
-
ErrorCode.DB_QUERY_ERROR,
|
94
|
-
detail=str(e),
|
95
|
-
source_function="DatabaseService.get_entity",
|
96
|
-
original_error=e
|
97
|
-
)
|
27
|
+
# 필터 조건 적용
|
28
|
+
if filters:
|
29
|
+
conditions = [getattr(model, key) == value for key, value in filters.items()]
|
30
|
+
query = query.where(and_(*conditions))
|
98
31
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
skip: int = 0,
|
104
|
-
limit: int = 100
|
105
|
-
) -> List[Any]:
|
106
|
-
"""엔티티 목록을 조회합니다.
|
107
|
-
|
108
|
-
Args:
|
109
|
-
model: 모델 클래스
|
110
|
-
filters: 필터 조건
|
111
|
-
skip: 건너뛸 레코드 수
|
112
|
-
limit: 조회할 최대 레코드 수
|
113
|
-
|
114
|
-
Returns:
|
115
|
-
엔티티 목록
|
116
|
-
|
117
|
-
Raises:
|
118
|
-
CustomException: 조회 실패 시
|
119
|
-
"""
|
120
|
-
try:
|
121
|
-
stmt = select(model)
|
122
|
-
if filters:
|
123
|
-
stmt = stmt.filter_by(**filters)
|
124
|
-
stmt = stmt.offset(skip).limit(limit)
|
125
|
-
result = await self.session.execute(stmt)
|
126
|
-
return result.scalars().all()
|
127
|
-
except Exception as e:
|
128
|
-
raise CustomException(
|
129
|
-
ErrorCode.DB_QUERY_ERROR,
|
130
|
-
detail=str(e),
|
131
|
-
source_function="DatabaseService.list_entities",
|
132
|
-
original_error=e
|
133
|
-
)
|
32
|
+
# 조인 로딩 적용
|
33
|
+
if joins:
|
34
|
+
for join_option in joins:
|
35
|
+
query = query.options(join_option)
|
134
36
|
|
135
|
-
|
136
|
-
|
137
|
-
entity: Base,
|
138
|
-
update_data: Dict[str, Any]
|
139
|
-
) -> Any:
|
140
|
-
"""엔티티를 수정합니다.
|
141
|
-
|
142
|
-
Args:
|
143
|
-
entity: 수정할 엔티티
|
144
|
-
update_data: 수정할 데이터
|
145
|
-
|
146
|
-
Returns:
|
147
|
-
수정된 엔티티
|
148
|
-
|
149
|
-
Raises:
|
150
|
-
CustomException: 수정 실패 시
|
151
|
-
"""
|
152
|
-
try:
|
153
|
-
for key, value in update_data.items():
|
154
|
-
setattr(entity, key, value)
|
155
|
-
await self.session.flush()
|
156
|
-
await self.session.refresh(entity)
|
157
|
-
return entity
|
158
|
-
except IntegrityError as e:
|
159
|
-
await self.session.rollback()
|
160
|
-
raise CustomException(
|
161
|
-
ErrorCode.DB_INTEGRITY_ERROR,
|
162
|
-
detail=str(e),
|
163
|
-
source_function="DatabaseService.update_entity",
|
164
|
-
original_error=e
|
165
|
-
)
|
166
|
-
except Exception as e:
|
167
|
-
await self.session.rollback()
|
168
|
-
raise CustomException(
|
169
|
-
ErrorCode.DB_UPDATE_ERROR,
|
170
|
-
detail=str(e),
|
171
|
-
source_function="DatabaseService.update_entity",
|
172
|
-
original_error=e
|
173
|
-
)
|
37
|
+
# 페이지네이션 적용
|
38
|
+
query = query.limit(limit).offset(skip)
|
174
39
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
soft_delete: 소프트 삭제 여부
|
185
|
-
|
186
|
-
Returns:
|
187
|
-
삭제 성공 여부
|
188
|
-
|
189
|
-
Raises:
|
190
|
-
CustomException: 삭제 실패 시
|
191
|
-
"""
|
192
|
-
try:
|
193
|
-
if soft_delete:
|
194
|
-
entity.is_deleted = True
|
195
|
-
await self.session.flush()
|
196
|
-
else:
|
197
|
-
await self.session.delete(entity)
|
198
|
-
await self.session.flush()
|
199
|
-
return True
|
200
|
-
except Exception as e:
|
201
|
-
await self.session.rollback()
|
202
|
-
raise CustomException(
|
203
|
-
ErrorCode.DB_DELETE_ERROR,
|
204
|
-
detail=str(e),
|
205
|
-
source_function="DatabaseService.delete_entity",
|
206
|
-
original_error=e
|
207
|
-
)
|
40
|
+
result = await session.execute(query)
|
41
|
+
return result.scalars().unique().all()
|
42
|
+
except SQLAlchemyError as e:
|
43
|
+
raise CustomException(
|
44
|
+
ErrorCode.DB_READ_ERROR,
|
45
|
+
detail=f"{model.__name__}|{str(e)}",
|
46
|
+
source_function="database.list_entities",
|
47
|
+
original_error=e
|
48
|
+
)
|
aiteamutils/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
"""버전 정보"""
|
2
|
-
__version__ = "0.2.
|
2
|
+
__version__ = "0.2.60"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
aiteamutils/__init__.py,sha256=h7iFifWvRlaComsk4VPapRr16YhD19Kp1LuBzLHqdgQ,1170
|
2
|
+
aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,2708
|
3
|
+
aiteamutils/base_repository.py,sha256=ir2ftophKKd_9BQ9B0LbszMqUyFeWkQ1MmFF6WBLcWc,1682
|
4
|
+
aiteamutils/base_service.py,sha256=Ncomi5yBUL4oHrMx_J1yUKVrUIYo6YmnYJWAWOIwQx8,2217
|
5
|
+
aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
|
6
|
+
aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
|
7
|
+
aiteamutils/database.py,sha256=MtmrX_pDzKFQM-P3OAfm2mALvhRg-v5JWtGBoiegeU0,1484
|
8
|
+
aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
|
9
|
+
aiteamutils/exceptions.py,sha256=_lKWXq_ujNj41xN6LDE149PwsecAP7lgYWbOBbLOntg,15368
|
10
|
+
aiteamutils/security.py,sha256=xFVrjttxwXB1TTjqgRQQgQJQohQBT28vuW8FVLjvi-M,10103
|
11
|
+
aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
|
12
|
+
aiteamutils/version.py,sha256=W0y4-03EvxLX9F9qSLA2iCovEsiyhT86gHqCOTRNhGU,42
|
13
|
+
aiteamutils-0.2.60.dist-info/METADATA,sha256=Gu1bs923MYkC_xKFhMKgS8PrSsK2ne2UJRUxBBeflRI,1718
|
14
|
+
aiteamutils-0.2.60.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
aiteamutils-0.2.60.dist-info/RECORD,,
|
aiteamutils/dependencies.py
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
"""의존성 관리 모듈."""
|
2
|
-
from typing import Type, TypeVar, Dict, Any, Optional, Callable, List, AsyncGenerator
|
3
|
-
from fastapi import Request, Depends
|
4
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
-
|
6
|
-
from .exceptions import CustomException, ErrorCode
|
7
|
-
from .base_service import BaseService
|
8
|
-
from .base_repository import BaseRepository
|
9
|
-
from .database import DatabaseService
|
10
|
-
|
11
|
-
T = TypeVar("T", bound=BaseService)
|
12
|
-
R = TypeVar("R", bound=BaseRepository)
|
13
|
-
|
14
|
-
_service_registry: Dict[str, Dict[str, Any]] = {}
|
15
|
-
_session_provider = None
|
16
|
-
|
17
|
-
__all__ = [
|
18
|
-
"setup_dependencies",
|
19
|
-
"register_service",
|
20
|
-
"get_service"
|
21
|
-
]
|
22
|
-
|
23
|
-
def setup_dependencies(session_provider: Callable[[], AsyncGenerator[AsyncSession, None]]) -> None:
|
24
|
-
"""의존성 설정을 초기화합니다.
|
25
|
-
|
26
|
-
Args:
|
27
|
-
session_provider: 데이터베이스 세션을 제공하는 함수
|
28
|
-
"""
|
29
|
-
global _session_provider
|
30
|
-
_session_provider = session_provider
|
31
|
-
|
32
|
-
def register_service(
|
33
|
-
service_class: Type[T],
|
34
|
-
repository_class: Optional[Type[R]] = None,
|
35
|
-
**kwargs
|
36
|
-
) -> None:
|
37
|
-
"""서비스를 등록합니다.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
service_class: 서비스 클래스
|
41
|
-
repository_class: 저장소 클래스 (선택)
|
42
|
-
**kwargs: 추가 의존성
|
43
|
-
"""
|
44
|
-
if _session_provider is None:
|
45
|
-
raise CustomException(
|
46
|
-
ErrorCode.INTERNAL_ERROR,
|
47
|
-
detail="Dependencies not initialized",
|
48
|
-
source_function="dependencies.register_service"
|
49
|
-
)
|
50
|
-
|
51
|
-
service_name = service_class.__name__
|
52
|
-
_service_registry[service_name] = {
|
53
|
-
"service_class": service_class,
|
54
|
-
"repository_class": repository_class,
|
55
|
-
"dependencies": kwargs
|
56
|
-
}
|
57
|
-
|
58
|
-
async def _get_service(
|
59
|
-
service_name: str,
|
60
|
-
session: AsyncSession,
|
61
|
-
request: Request
|
62
|
-
) -> BaseService:
|
63
|
-
"""서비스 인스턴스를 생성합니다.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
service_name: 서비스 이름
|
67
|
-
session: 데이터베이스 세션
|
68
|
-
request: FastAPI 요청 객체
|
69
|
-
|
70
|
-
Returns:
|
71
|
-
BaseService: 서비스 인스턴스
|
72
|
-
|
73
|
-
Raises:
|
74
|
-
CustomException: 서비스 생성 실패 시
|
75
|
-
"""
|
76
|
-
try:
|
77
|
-
service_info = _service_registry.get(service_name)
|
78
|
-
if not service_info:
|
79
|
-
raise CustomException(
|
80
|
-
ErrorCode.SERVICE_NOT_FOUND,
|
81
|
-
detail=service_name,
|
82
|
-
source_function="dependencies._get_service"
|
83
|
-
)
|
84
|
-
|
85
|
-
service_class = service_info["service_class"]
|
86
|
-
repository_class = service_info["repository_class"]
|
87
|
-
dependencies = service_info["dependencies"]
|
88
|
-
|
89
|
-
# 데이터베이스 서비스 생성
|
90
|
-
db_service = DatabaseService(session=session)
|
91
|
-
|
92
|
-
# 저장소 인스턴스 생성
|
93
|
-
repository = None
|
94
|
-
if repository_class:
|
95
|
-
repository = repository_class(session=session)
|
96
|
-
|
97
|
-
# 서비스 인스턴스 생성
|
98
|
-
service = service_class(
|
99
|
-
db=db_service,
|
100
|
-
repository=repository,
|
101
|
-
request=request,
|
102
|
-
**dependencies
|
103
|
-
)
|
104
|
-
|
105
|
-
return service
|
106
|
-
|
107
|
-
except CustomException as e:
|
108
|
-
raise e
|
109
|
-
except Exception as e:
|
110
|
-
raise CustomException(
|
111
|
-
ErrorCode.INTERNAL_ERROR,
|
112
|
-
detail=str(e),
|
113
|
-
source_function="dependencies._get_service",
|
114
|
-
original_error=e
|
115
|
-
)
|
116
|
-
|
117
|
-
def get_service(service_name: str) -> Callable:
|
118
|
-
"""서비스 의존성을 반환합니다.
|
119
|
-
|
120
|
-
Args:
|
121
|
-
service_name: 서비스 이름
|
122
|
-
|
123
|
-
Returns:
|
124
|
-
Callable: 서비스 의존성 함수
|
125
|
-
"""
|
126
|
-
if _session_provider is None:
|
127
|
-
raise CustomException(
|
128
|
-
ErrorCode.INTERNAL_ERROR,
|
129
|
-
detail="Dependencies not initialized",
|
130
|
-
source_function="dependencies.get_service"
|
131
|
-
)
|
132
|
-
|
133
|
-
async def _get_service_dependency(
|
134
|
-
request: Request,
|
135
|
-
session: AsyncSession = Depends(_session_provider)
|
136
|
-
) -> BaseService:
|
137
|
-
return await _get_service(service_name, session, request)
|
138
|
-
return _get_service_dependency
|
@@ -1,16 +0,0 @@
|
|
1
|
-
aiteamutils/__init__.py,sha256=U9UEyU0AqpLZzbPJiBvwDEh-s2405y1IeWZ0zCqFSl0,1307
|
2
|
-
aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,2708
|
3
|
-
aiteamutils/base_repository.py,sha256=vqsundoN0h7FVvgqTBEnnJNMcFpvMK0s_nxBWdIYg-U,7846
|
4
|
-
aiteamutils/base_service.py,sha256=o4oeX__RmUFp5kV-fb4VwBhF8nTAZNMjoWJvK7G1Wmk,8615
|
5
|
-
aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
|
6
|
-
aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
|
7
|
-
aiteamutils/database.py,sha256=x0x5gnSyGfwo_klL9O65RnGOQID6c9tH2miwFveVyoE,6326
|
8
|
-
aiteamutils/dependencies.py,sha256=Qoy_M7r65Fv3Y8RMVBi81QLdfjfWIk-pm7O5_Przsa4,4084
|
9
|
-
aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
|
10
|
-
aiteamutils/exceptions.py,sha256=_lKWXq_ujNj41xN6LDE149PwsecAP7lgYWbOBbLOntg,15368
|
11
|
-
aiteamutils/security.py,sha256=xFVrjttxwXB1TTjqgRQQgQJQohQBT28vuW8FVLjvi-M,10103
|
12
|
-
aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
|
13
|
-
aiteamutils/version.py,sha256=_ql6JzqtHA14js-9_8pUBJDK_7BZm5uHqA1j4jfryCs,42
|
14
|
-
aiteamutils-0.2.59.dist-info/METADATA,sha256=OOT0-1lTWlUQv7fL2MtELHAUPSj18nqiaiXRyFc8QoQ,1718
|
15
|
-
aiteamutils-0.2.59.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
-
aiteamutils-0.2.59.dist-info/RECORD,,
|
File without changes
|