aiteamutils 0.2.72__tar.gz → 0.2.75__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.72
3
+ Version: 0.2.75
4
4
  Summary: AI Team Utilities
5
5
  Project-URL: Homepage, https://github.com/yourusername/aiteamutils
6
6
  Project-URL: Issues, https://github.com/yourusername/aiteamutils/issues
@@ -23,10 +23,17 @@ class BaseColumn(Base):
23
23
  doc="ULID",
24
24
  nullable=False
25
25
  )
26
- created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
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
+