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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.73
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
+