aiteamutils 0.2.73__tar.gz → 0.2.75__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.
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/PKG-INFO +1 -1
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/base_model.py +9 -2
- aiteamutils-0.2.75/aiteamutils/base_repository.py +141 -0
- aiteamutils-0.2.75/aiteamutils/base_service.py +258 -0
- aiteamutils-0.2.75/aiteamutils/database.py +583 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/exceptions.py +6 -2
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/security.py +28 -23
- aiteamutils-0.2.75/aiteamutils/version.py +2 -0
- aiteamutils-0.2.73/aiteamutils/base_repository.py +0 -63
- aiteamutils-0.2.73/aiteamutils/base_service.py +0 -77
- aiteamutils-0.2.73/aiteamutils/database.py +0 -242
- aiteamutils-0.2.73/aiteamutils/version.py +0 -2
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/.cursorrules +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/.gitignore +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/README.md +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/__init__.py +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/cache.py +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/config.py +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/enums.py +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/aiteamutils/validators.py +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/pyproject.toml +0 -0
- {aiteamutils-0.2.73 → aiteamutils-0.2.75}/setup.py +0 -0
@@ -23,10 +23,17 @@ class BaseColumn(Base):
|
|
23
23
|
doc="ULID",
|
24
24
|
nullable=False
|
25
25
|
)
|
26
|
-
created_at: Mapped[datetime] = mapped_column(
|
26
|
+
created_at: Mapped[datetime] = mapped_column(
|
27
|
+
default=datetime.utcnow,
|
28
|
+
index=True
|
29
|
+
)
|
27
30
|
updated_at: Mapped[datetime] = mapped_column(
|
28
31
|
default=datetime.utcnow,
|
29
|
-
onupdate=datetime.utcnow
|
32
|
+
onupdate=datetime.utcnow,
|
33
|
+
index=True
|
34
|
+
)
|
35
|
+
deleted_at: Mapped[datetime] = mapped_column(
|
36
|
+
default=None,
|
30
37
|
)
|
31
38
|
is_deleted: Mapped[bool] = mapped_column(
|
32
39
|
default=False,
|
@@ -0,0 +1,141 @@
|
|
1
|
+
#기본 라이브러리
|
2
|
+
from typing import TypeVar, Generic, Type, Any, Dict, List, Optional
|
3
|
+
from sqlalchemy.orm import DeclarativeBase
|
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 (
|
11
|
+
list_entities,
|
12
|
+
get_entity,
|
13
|
+
create_entity,
|
14
|
+
update_entity,
|
15
|
+
delete_entity
|
16
|
+
)
|
17
|
+
|
18
|
+
ModelType = TypeVar("ModelType", bound=DeclarativeBase)
|
19
|
+
|
20
|
+
class BaseRepository(Generic[ModelType]):
|
21
|
+
def __init__(self, session: AsyncSession, model: Type[ModelType]):
|
22
|
+
self._session = session
|
23
|
+
self.model = model
|
24
|
+
|
25
|
+
@property
|
26
|
+
def session(self) -> AsyncSession:
|
27
|
+
return self._session
|
28
|
+
|
29
|
+
@session.setter
|
30
|
+
def session(self, value: AsyncSession):
|
31
|
+
if value is None:
|
32
|
+
raise CustomException(
|
33
|
+
ErrorCode.DB_CONNECTION_ERROR,
|
34
|
+
detail="Session cannot be None",
|
35
|
+
source_function=f"{self.__class__.__name__}.session"
|
36
|
+
)
|
37
|
+
self._session = value
|
38
|
+
|
39
|
+
#######################
|
40
|
+
# 입력 및 수정, 삭제 #
|
41
|
+
#######################
|
42
|
+
async def create(self, entity_data: Dict[str, Any]) -> ModelType:
|
43
|
+
try:
|
44
|
+
return await create_entity(
|
45
|
+
session=self.session,
|
46
|
+
model=self.model,
|
47
|
+
entity_data=entity_data
|
48
|
+
)
|
49
|
+
except CustomException as e:
|
50
|
+
raise e
|
51
|
+
except Exception as e:
|
52
|
+
raise CustomException(
|
53
|
+
ErrorCode.INTERNAL_ERROR,
|
54
|
+
detail=str(e),
|
55
|
+
source_function=f"{self.__class__.__name__}.create",
|
56
|
+
original_error=e
|
57
|
+
)
|
58
|
+
|
59
|
+
async def update(
|
60
|
+
self,
|
61
|
+
update_data: Dict[str, Any],
|
62
|
+
conditions: Dict[str, Any]
|
63
|
+
) -> ModelType:
|
64
|
+
try:
|
65
|
+
return await update_entity(
|
66
|
+
session=self.session,
|
67
|
+
model=self.model,
|
68
|
+
update_data=update_data,
|
69
|
+
conditions=conditions
|
70
|
+
)
|
71
|
+
except CustomException as e:
|
72
|
+
raise e
|
73
|
+
|
74
|
+
async def delete(
|
75
|
+
self,
|
76
|
+
conditions: Dict[str, Any]
|
77
|
+
) -> None:
|
78
|
+
await delete_entity(
|
79
|
+
session=self.session,
|
80
|
+
model=self.model,
|
81
|
+
conditions=conditions
|
82
|
+
)
|
83
|
+
|
84
|
+
#########################
|
85
|
+
# 조회 및 검색 메서드 #
|
86
|
+
#########################
|
87
|
+
async def list(
|
88
|
+
self,
|
89
|
+
skip: int = 0,
|
90
|
+
limit: int = 100,
|
91
|
+
filters: Optional[Dict[str, Any]] = None,
|
92
|
+
explicit_joins: Optional[List[Any]] = None,
|
93
|
+
loading_joins: Optional[List[Any]] = None
|
94
|
+
) -> List[ModelType]:
|
95
|
+
"""
|
96
|
+
엔티티 목록 조회.
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
# 기본 CRUD 작업 호출
|
100
|
+
return await list_entities(
|
101
|
+
session=self.session,
|
102
|
+
model=self.model,
|
103
|
+
skip=skip,
|
104
|
+
limit=limit,
|
105
|
+
filters=filters,
|
106
|
+
explicit_joins=explicit_joins,
|
107
|
+
loading_joins=loading_joins
|
108
|
+
)
|
109
|
+
except CustomException as e:
|
110
|
+
raise e
|
111
|
+
except Exception as e:
|
112
|
+
raise CustomException(
|
113
|
+
ErrorCode.INTERNAL_ERROR,
|
114
|
+
detail=str(e),
|
115
|
+
source_function=f"{self.__class__.__name__}.list",
|
116
|
+
original_error=e
|
117
|
+
)
|
118
|
+
|
119
|
+
async def get(
|
120
|
+
self,
|
121
|
+
conditions: Dict[str, Any] | None = None,
|
122
|
+
explicit_joins: Optional[List[Any]] = None,
|
123
|
+
loading_joins: Optional[List[Any]] = None
|
124
|
+
) -> ModelType:
|
125
|
+
try:
|
126
|
+
return await get_entity(
|
127
|
+
session=self.session,
|
128
|
+
model=self.model,
|
129
|
+
conditions=conditions,
|
130
|
+
explicit_joins=explicit_joins,
|
131
|
+
loading_joins=loading_joins
|
132
|
+
)
|
133
|
+
except CustomException as e:
|
134
|
+
raise e
|
135
|
+
except Exception as e:
|
136
|
+
raise CustomException(
|
137
|
+
ErrorCode.INTERNAL_ERROR,
|
138
|
+
detail=str(e),
|
139
|
+
source_function=f"{self.__class__.__name__}.get",
|
140
|
+
original_error=e
|
141
|
+
)
|
@@ -0,0 +1,258 @@
|
|
1
|
+
#기본 라이브러리
|
2
|
+
from fastapi import Request
|
3
|
+
from typing import TypeVar, Generic, Type, Dict, Any, Union, List, Optional
|
4
|
+
from sqlalchemy.orm import DeclarativeBase
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
+
from datetime import datetime
|
7
|
+
from ulid import ULID
|
8
|
+
|
9
|
+
#패키지 라이브러리
|
10
|
+
from .exceptions import ErrorCode, CustomException
|
11
|
+
from .base_repository import BaseRepository
|
12
|
+
from .database import (
|
13
|
+
process_response,
|
14
|
+
validate_unique_fields
|
15
|
+
)
|
16
|
+
from .security import hash_password
|
17
|
+
ModelType = TypeVar("ModelType", bound=DeclarativeBase)
|
18
|
+
|
19
|
+
class BaseService(Generic[ModelType]):
|
20
|
+
##################
|
21
|
+
# 1. 초기화 영역 #
|
22
|
+
##################
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
model: Type[ModelType],
|
26
|
+
repository: BaseRepository[ModelType],
|
27
|
+
db_session: AsyncSession,
|
28
|
+
additional_models: Dict[str, Type[DeclarativeBase]] = None,
|
29
|
+
):
|
30
|
+
self.model = model
|
31
|
+
self.repository = repository
|
32
|
+
self.db_session = db_session
|
33
|
+
self.additional_models = additional_models or {},
|
34
|
+
|
35
|
+
#######################
|
36
|
+
# 입력 및 수정, 삭제 #
|
37
|
+
#######################
|
38
|
+
async def create(
|
39
|
+
self,
|
40
|
+
entity_data: Dict[str, Any],
|
41
|
+
model_name: str | None = None,
|
42
|
+
response_model: Any = None,
|
43
|
+
exclude_entities: List[str] | None = None,
|
44
|
+
unique_check: List[Dict[str, Any]] | None = None,
|
45
|
+
fk_check: List[Dict[str, Any]] | None = None,
|
46
|
+
) -> ModelType:
|
47
|
+
|
48
|
+
try:
|
49
|
+
# 고유 검사 수행
|
50
|
+
if unique_check:
|
51
|
+
await validate_unique_fields(self.db_session, unique_check, find_value=True)
|
52
|
+
# 외래 키 검사 수행
|
53
|
+
if fk_check:
|
54
|
+
await validate_unique_fields(self.db_session, fk_check, find_value=False)
|
55
|
+
# 비밀번호가 있으면 해시 처리
|
56
|
+
if "password" in entity_data:
|
57
|
+
entity_data["password"] = hash_password(entity_data["password"])
|
58
|
+
# 제외할 엔티티가 있으면 제외
|
59
|
+
if exclude_entities:
|
60
|
+
entity_data = {k: v for k, v in entity_data.items() if k not in exclude_entities}
|
61
|
+
|
62
|
+
# repository의 create 메서드를 트랜잭션 내에서 실행
|
63
|
+
return await self.repository.create(entity_data)
|
64
|
+
except CustomException as e:
|
65
|
+
raise e
|
66
|
+
except Exception as e:
|
67
|
+
# 다른 예외 처리
|
68
|
+
raise CustomException(
|
69
|
+
ErrorCode.INTERNAL_ERROR,
|
70
|
+
detail=str(e),
|
71
|
+
source_function=f"{self.__class__.__name__}.create",
|
72
|
+
original_error=e
|
73
|
+
)
|
74
|
+
|
75
|
+
async def update(
|
76
|
+
self,
|
77
|
+
ulid: str | None = None,
|
78
|
+
update_data: Dict[str, Any] | None = None,
|
79
|
+
conditions: Dict[str, Any] | None = None,
|
80
|
+
unique_check: List[Dict[str, Any]] | None = None,
|
81
|
+
exclude_entities: List[str] | None = None
|
82
|
+
) -> ModelType:
|
83
|
+
try:
|
84
|
+
# 고유 검사 수행
|
85
|
+
if unique_check:
|
86
|
+
await validate_unique_fields(self.db_session, unique_check, find_value=True)
|
87
|
+
# 비밀번호가 있으면 해시 처리
|
88
|
+
if "password" in update_data:
|
89
|
+
update_data["password"] = hash_password(update_data["password"])
|
90
|
+
# 제외할 엔티티가 있으면 제외
|
91
|
+
if exclude_entities:
|
92
|
+
update_data = {k: v for k, v in update_data.items() if k not in exclude_entities}
|
93
|
+
|
94
|
+
if not ulid and not conditions:
|
95
|
+
raise CustomException(
|
96
|
+
ErrorCode.INVALID_INPUT,
|
97
|
+
detail="Either 'ulid' or 'conditions' must be provided.",
|
98
|
+
source_function="database.update_entity"
|
99
|
+
)
|
100
|
+
|
101
|
+
# ulid로 조건 생성
|
102
|
+
if ulid:
|
103
|
+
if not ULID.from_str(ulid):
|
104
|
+
raise CustomException(
|
105
|
+
ErrorCode.VALIDATION_ERROR,
|
106
|
+
detail=ulid,
|
107
|
+
source_function=f"{self.__class__.__name__}.update"
|
108
|
+
)
|
109
|
+
|
110
|
+
conditions = {"ulid": ulid}
|
111
|
+
|
112
|
+
return await self.repository.update(
|
113
|
+
update_data=update_data,
|
114
|
+
conditions=conditions
|
115
|
+
)
|
116
|
+
except CustomException as e:
|
117
|
+
raise e
|
118
|
+
except Exception as e:
|
119
|
+
raise CustomException(
|
120
|
+
ErrorCode.INTERNAL_ERROR,
|
121
|
+
detail=str(e),
|
122
|
+
source_function=f"{self.__class__.__name__}.update",
|
123
|
+
original_error=e
|
124
|
+
)
|
125
|
+
|
126
|
+
async def delete(
|
127
|
+
self,
|
128
|
+
ulid: str | None = None,
|
129
|
+
conditions: Dict[str, Any] | None = None
|
130
|
+
) -> None:
|
131
|
+
try:
|
132
|
+
if not ULID.from_str(ulid):
|
133
|
+
raise CustomException(
|
134
|
+
ErrorCode.VALIDATION_ERROR,
|
135
|
+
detail=ulid,
|
136
|
+
source_function=f"{self.__class__.__name__}.delete"
|
137
|
+
)
|
138
|
+
|
139
|
+
if not ulid and not conditions:
|
140
|
+
raise CustomException(
|
141
|
+
ErrorCode.INVALID_INPUT,
|
142
|
+
detail="Either 'ulid' or 'conditions' must be provided.",
|
143
|
+
source_function="database.update_entity"
|
144
|
+
)
|
145
|
+
|
146
|
+
# ulid로 조건 생성
|
147
|
+
if ulid:
|
148
|
+
conditions = {"ulid": ulid}
|
149
|
+
|
150
|
+
conditions["is_deleted"] = False
|
151
|
+
|
152
|
+
return await self.repository.delete(
|
153
|
+
conditions=conditions
|
154
|
+
)
|
155
|
+
except CustomException as e:
|
156
|
+
raise e
|
157
|
+
except Exception as e:
|
158
|
+
raise CustomException(
|
159
|
+
ErrorCode.INTERNAL_ERROR,
|
160
|
+
detail=str(e),
|
161
|
+
source_function=f"{self.__class__.__name__}.delete",
|
162
|
+
original_error=e
|
163
|
+
)
|
164
|
+
|
165
|
+
#########################
|
166
|
+
# 조회 및 검색 메서드 #
|
167
|
+
#########################
|
168
|
+
async def list(
|
169
|
+
self,
|
170
|
+
skip: int = 0,
|
171
|
+
limit: int = 100,
|
172
|
+
filters: List[Dict[str, Any]] | None = None,
|
173
|
+
model_name: str | None = None,
|
174
|
+
response_model: Any = None,
|
175
|
+
explicit_joins: Optional[List[Any]] = None,
|
176
|
+
loading_joins: Optional[List[Any]] = None
|
177
|
+
) -> List[Dict[str, Any]]:
|
178
|
+
try:
|
179
|
+
# 모델 이름을 통한 동적 처리
|
180
|
+
if model_name:
|
181
|
+
if model_name not in self.additional_models:
|
182
|
+
raise CustomException(
|
183
|
+
ErrorCode.INVALID_REQUEST,
|
184
|
+
detail=f"Model {model_name} not registered",
|
185
|
+
source_function=f"{self.__class__.__name__}.list"
|
186
|
+
)
|
187
|
+
model = self.additional_models[model_name]
|
188
|
+
entities = await self.repository.list(
|
189
|
+
skip=skip,
|
190
|
+
limit=limit,
|
191
|
+
filters=filters,
|
192
|
+
model=model
|
193
|
+
)
|
194
|
+
return [process_response(entity, response_model) for entity in entities]
|
195
|
+
|
196
|
+
entities = await self.repository.list(
|
197
|
+
skip=skip,
|
198
|
+
limit=limit,
|
199
|
+
filters=filters,
|
200
|
+
explicit_joins=explicit_joins,
|
201
|
+
loading_joins=loading_joins
|
202
|
+
)
|
203
|
+
return [process_response(entity, response_model) for entity in entities]
|
204
|
+
|
205
|
+
except CustomException as e:
|
206
|
+
raise e
|
207
|
+
except Exception as e:
|
208
|
+
raise CustomException(
|
209
|
+
ErrorCode.INTERNAL_ERROR,
|
210
|
+
source_function=f"{self.__class__.__name__}.list",
|
211
|
+
original_error=e
|
212
|
+
)
|
213
|
+
|
214
|
+
async def get(
|
215
|
+
self,
|
216
|
+
ulid: str,
|
217
|
+
model_name: str | None = None,
|
218
|
+
response_model: Any = None,
|
219
|
+
conditions: Dict[str, Any] | None = None,
|
220
|
+
explicit_joins: Optional[List[Any]] = None,
|
221
|
+
loading_joins: Optional[List[Any]] = None
|
222
|
+
):
|
223
|
+
try:
|
224
|
+
if not ulid and not conditions:
|
225
|
+
raise CustomException(
|
226
|
+
ErrorCode.INVALID_INPUT,
|
227
|
+
detail="Either 'ulid' or 'conditions' must be provided.",
|
228
|
+
source_function="database.update_entity"
|
229
|
+
)
|
230
|
+
|
231
|
+
# ulid로 조건 생성
|
232
|
+
if ulid:
|
233
|
+
if not ULID.from_str(ulid):
|
234
|
+
raise CustomException(
|
235
|
+
ErrorCode.VALIDATION_ERROR,
|
236
|
+
detail=ulid,
|
237
|
+
source_function=f"{self.__class__.__name__}.update"
|
238
|
+
)
|
239
|
+
|
240
|
+
conditions = {"ulid": ulid}
|
241
|
+
|
242
|
+
entity = await self.repository.get(
|
243
|
+
conditions=conditions,
|
244
|
+
explicit_joins=explicit_joins,
|
245
|
+
loading_joins=loading_joins
|
246
|
+
)
|
247
|
+
return process_response(entity, response_model)
|
248
|
+
|
249
|
+
except CustomException as e:
|
250
|
+
raise e
|
251
|
+
except Exception as e:
|
252
|
+
raise CustomException(
|
253
|
+
ErrorCode.INTERNAL_ERROR,
|
254
|
+
detail=str(e),
|
255
|
+
source_function=f"{self.__class__.__name__}.get",
|
256
|
+
original_error=e
|
257
|
+
)
|
258
|
+
|