aiteamutils 0.2.64__py3-none-any.whl → 0.2.66__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.
@@ -13,24 +13,8 @@ ModelType = TypeVar("ModelType", bound=DeclarativeBase)
13
13
 
14
14
  class BaseRepository(Generic[ModelType]):
15
15
  def __init__(self, session: AsyncSession, model: Type[ModelType]):
16
- if session is None:
17
- raise CustomException(
18
- ErrorCode.DB_CONNECTION_ERROR,
19
- detail="Database session is not set",
20
- source_function=f"{self.__class__.__name__}.session"
21
- )
22
- if model is None:
23
- raise CustomException(
24
- ErrorCode.DB_CONNECTION_ERROR,
25
- detail="Model is not set",
26
- source_function=f"{self.__class__.__name__}.model"
27
- )
28
- print(f"BaseRepository.__init__() called with session: {session} and model: {model}")
29
-
30
16
  self._session = session
31
17
  self.model = model
32
-
33
- print(f"BaseRepository initialized with session: {session} and model: {model}")
34
18
 
35
19
  @property
36
20
  def session(self) -> AsyncSession:
@@ -51,7 +35,7 @@ class BaseRepository(Generic[ModelType]):
51
35
  skip: int = 0,
52
36
  limit: int = 100,
53
37
  filters: Optional[Dict[str, Any]] = None,
54
- joins: Optional[List[Any]] = None
38
+ joins: Optional[List[Any]] = None,
55
39
  ) -> List[ModelType]:
56
40
  """
57
41
  엔티티 목록 조회.
@@ -4,9 +4,14 @@ from typing import TypeVar, Generic, Type, Dict, Any, Union, List
4
4
  from sqlalchemy.orm import DeclarativeBase
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
6
  from datetime import datetime
7
+
7
8
  #패키지 라이브러리
8
9
  from .exceptions import ErrorCode, CustomException
9
10
  from .base_repository import BaseRepository
11
+ from .database import (
12
+ process_response,
13
+ build_search_filters
14
+ )
10
15
 
11
16
  ModelType = TypeVar("ModelType", bound=DeclarativeBase)
12
17
 
@@ -18,28 +23,13 @@ class BaseService(Generic[ModelType]):
18
23
  self,
19
24
  model: Type[ModelType],
20
25
  repository: BaseRepository[ModelType],
21
- db_session: AsyncSession
26
+ db_session: AsyncSession,
27
+ additional_models: Dict[str, Type[DeclarativeBase]] = None,
22
28
  ):
23
- if not repository:
24
- raise CustomException(
25
- ErrorCode.INVALID_REQUEST,
26
- detail="Repository is not set",
27
- source_function=f"{self.__class__.__name__}.__init__"
28
- )
29
- if not db_session:
30
- raise CustomException(
31
- ErrorCode.INVALID_REQUEST,
32
- detail="Database session is not set",
33
- source_function=f"{self.__class__.__name__}.__init__"
34
- )
35
- print(f"BaseService.__init__() called with repository: {repository} and db_session: {db_session}")
36
-
37
-
38
29
  self.model = model
39
30
  self.repository = repository
40
31
  self.db_session = db_session
41
-
42
- print(f"BaseService initialized with repository: {repository} and db_session: {db_session}")
32
+ self.additional_models = additional_models or {},
43
33
 
44
34
  async def list(
45
35
  self,
@@ -48,8 +38,19 @@ class BaseService(Generic[ModelType]):
48
38
  filters: Dict[str, Any] | None = None,
49
39
  search_params: Dict[str, Any] | None = None,
50
40
  model_name: str | None = None,
41
+ request: Request | None = None,
42
+ response_model: Any = None
51
43
  ) -> List[Dict[str, Any]]:
52
44
  try:
45
+ # 검색 조건 처리 및 필터 병합
46
+ if search_params:
47
+ search_filters = build_search_filters(request, search_params)
48
+ if filters:
49
+ # 기존 filters와 search_filters 병합 (search_filters가 우선 적용)
50
+ filters.update(search_filters)
51
+ else:
52
+ filters = search_filters
53
+
53
54
  # 모델 이름을 통한 동적 처리
54
55
  if model_name:
55
56
  if model_name not in self.additional_models:
aiteamutils/database.py CHANGED
@@ -4,14 +4,184 @@ from sqlalchemy.ext.asyncio import AsyncSession
4
4
  from sqlalchemy import select, and_
5
5
  from sqlalchemy.orm import DeclarativeBase
6
6
  from sqlalchemy.exc import SQLAlchemyError
7
-
8
- ModelType = TypeVar("ModelType", bound=DeclarativeBase)
7
+ from datetime import datetime
9
8
 
10
9
  #패키지 라이브러리
11
10
  from .exceptions import ErrorCode, CustomException
12
11
 
12
+
13
+ ModelType = TypeVar("ModelType", bound=DeclarativeBase)
14
+
15
+ ##################
16
+ # 전처리 #
17
+ ##################
18
+
19
+
20
+
13
21
  ##################
14
- # 1. 쿼리 실행 #
22
+ # 응답 처리 #
23
+ ##################
24
+ def process_columns(
25
+ entity: ModelType,
26
+ exclude_extra_data: bool = True
27
+ ) -> Dict[str, Any]:
28
+ """엔티티의 컬럼들을 처리합니다.
29
+
30
+ Args:
31
+ entity (ModelType): 처리할 엔티티
32
+ exclude_extra_data (bool, optional): extra_data 컬럼 제외 여부. Defaults to True.
33
+
34
+ Returns:
35
+ Dict[str, Any]: 처리된 컬럼 데이터
36
+ """
37
+ result = {}
38
+ for column in entity.__table__.columns:
39
+ if exclude_extra_data and column.name == 'extra_data':
40
+ continue
41
+
42
+ # 필드 값 처리
43
+ if hasattr(entity, column.name):
44
+ value = getattr(entity, column.name)
45
+ if isinstance(value, datetime):
46
+ value = value.isoformat()
47
+ result[column.name] = value
48
+ elif hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
49
+ result[column.name] = entity.extra_data.get(column.name)
50
+ else:
51
+ result[column.name] = None
52
+
53
+ # extra_data의 내용을 최상위 레벨로 업데이트
54
+ if hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
55
+ result.update(entity.extra_data or {})
56
+
57
+ return result
58
+
59
+ def process_response(
60
+ entity: ModelType,
61
+ response_model: Any = None
62
+ ) -> Dict[str, Any]:
63
+ """응답 데이터를 처리합니다.
64
+ extra_data의 내용을 최상위 레벨로 변환하고, 라우터에서 선언한 응답 스키마에 맞게 데이터를 변환합니다.
65
+
66
+ Args:
67
+ entity (ModelType): 처리할 엔티티
68
+ response_model (Any, optional): 응답 스키마. Defaults to None.
69
+
70
+ Returns:
71
+ Dict[str, Any]: 처리된 엔티티 데이터
72
+ """
73
+ if not entity:
74
+ return None
75
+
76
+ # 모든 필드 처리
77
+ result = process_columns(entity)
78
+
79
+ # Relationship 처리 (이미 로드된 관계만 처리)
80
+ for relationship in entity.__mapper__.relationships:
81
+ if not relationship.key in entity.__dict__:
82
+ continue
83
+
84
+ try:
85
+ value = getattr(entity, relationship.key)
86
+ # response_model이 있는 경우 해당 필드의 annotation type을 가져옴
87
+ nested_response_model = None
88
+ if response_model and relationship.key in response_model.model_fields:
89
+ field_info = response_model.model_fields[relationship.key]
90
+ nested_response_model = field_info.annotation
91
+
92
+ if value is not None:
93
+ if isinstance(value, list):
94
+ result[relationship.key] = [
95
+ process_response(item, nested_response_model)
96
+ for item in value
97
+ ]
98
+ else:
99
+ result[relationship.key] = process_response(value, nested_response_model)
100
+ else:
101
+ result[relationship.key] = None
102
+ except Exception:
103
+ result[relationship.key] = None
104
+
105
+ # response_model이 있는 경우 필터링
106
+ if response_model:
107
+ # 현재 키 목록을 저장
108
+ current_keys = list(result.keys())
109
+ # response_model에 없는 키 제거
110
+ for key in current_keys:
111
+ if key not in response_model.model_fields:
112
+ result.pop(key)
113
+ # 모델 검증 및 업데이트
114
+ result.update(response_model(**result).model_dump())
115
+
116
+ return result
117
+
118
+
119
+ ##################
120
+ # 조건 처리 #
121
+ ##################
122
+ def build_search_filters(
123
+ request: Dict[str, Any],
124
+ search_params: Dict[str, Dict[str, Any]]
125
+ ) -> Dict[str, Any]:
126
+ """
127
+ 요청 데이터와 검색 파라미터를 기반으로 필터 조건을 생성합니다.
128
+
129
+ Args:
130
+ request: 요청 데이터 (key-value 형태).
131
+ search_params: 검색 조건 설정을 위한 파라미터.
132
+
133
+ Returns:
134
+ filters: 필터 조건 딕셔너리.
135
+ """
136
+ filters = {}
137
+ for key, param in search_params.items():
138
+ value = request.get(key)
139
+ if value is not None:
140
+ if param["like"]:
141
+ filters[key] = {"field": param["fields"][0], "operator": "like", "value": f"%{value}%"}
142
+ else:
143
+ filters[key] = {"field": param["fields"][0], "operator": "eq", "value": value}
144
+ return filters
145
+
146
+ def build_conditions(
147
+ filters: Dict[str, Any],
148
+ model: Type[ModelType]
149
+ ) -> List[Any]:
150
+ """
151
+ 필터 조건을 기반으로 SQLAlchemy 조건 리스트를 생성합니다.
152
+
153
+ Args:
154
+ filters: 필터 조건 딕셔너리.
155
+ model: SQLAlchemy 모델 클래스.
156
+
157
+ Returns:
158
+ List[Any]: SQLAlchemy 조건 리스트.
159
+ """
160
+ conditions = []
161
+ for filter_data in filters.values():
162
+ if "." in filter_data["field"]:
163
+ # 관계를 따라 암묵적으로 연결된 모델의 필드 가져오기
164
+ related_model_name, field_name = filter_data["field"].split(".")
165
+ relationship_property = getattr(model, related_model_name)
166
+ related_model = relationship_property.property.mapper.class_
167
+ field = getattr(related_model, field_name)
168
+ else:
169
+ # 현재 모델의 필드 가져오기
170
+ field = getattr(model, filter_data["field"])
171
+
172
+ # 조건 생성
173
+ operator = filter_data["operator"]
174
+ value = filter_data["value"]
175
+
176
+ if operator == "like":
177
+ conditions.append(field.ilike(f"%{value}%"))
178
+ elif operator == "eq":
179
+ conditions.append(field == value)
180
+
181
+ return conditions
182
+
183
+ ##################
184
+ # 쿼리 실행 #
15
185
  ##################
16
186
  async def list_entities(
17
187
  session: AsyncSession,
@@ -21,12 +191,38 @@ async def list_entities(
21
191
  filters: Optional[Dict[str, Any]] = None,
22
192
  joins: Optional[List[Any]] = None
23
193
  ) -> List[Dict[str, Any]]:
194
+ """
195
+ 엔터티 리스트를 필터 및 조건에 따라 가져오는 함수.
196
+
197
+ Args:
198
+ session: SQLAlchemy AsyncSession.
199
+ model: SQLAlchemy 모델.
200
+ skip: 페이지네이션 시작 위치.
201
+ limit: 페이지네이션 크기.
202
+ filters: 필터 조건 딕셔너리.
203
+ 예시:
204
+ filters = {
205
+ "search": {"field": "username", "operator": "like", "value": "%admin%"},
206
+ "name": {"field": "name", "operator": "like", "value": "%John%"},
207
+ "role_ulid": {"field": "role_ulid", "operator": "eq", "value": "1234"}
208
+ }
209
+
210
+ joins: 조인 옵션.
211
+ 예시:
212
+ joins = [
213
+ selectinload(YourModel.related_field), # 관련된 필드를 함께 로드
214
+ joinedload(YourModel.another_related_field) # 다른 관계된 필드를 조인
215
+ ]
216
+
217
+ Returns:
218
+ List[Dict[str, Any]]: 쿼리 결과 리스트.
219
+ """
24
220
  try:
25
221
  query = select(model)
26
222
 
27
223
  # 필터 조건 적용
28
224
  if filters:
29
- conditions = [getattr(model, key) == value for key, value in filters.items()]
225
+ conditions = build_conditions(filters, model)
30
226
  query = query.where(and_(*conditions))
31
227
 
32
228
  # 조인 로딩 적용
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.64"
2
+ __version__ = "0.2.66"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.64
3
+ Version: 0.2.66
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
@@ -1,15 +1,15 @@
1
1
  aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
2
  aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,2708
3
- aiteamutils/base_repository.py,sha256=oA91ZtAM8LvdULL0gBB1U-7sj4Up5qUoXh1HJctnTGw,2210
4
- aiteamutils/base_service.py,sha256=GZY9Xx-nu4EGOQuCissnulc9nGL5DpE2CypX5wj-I-o,2908
3
+ aiteamutils/base_repository.py,sha256=Vbz2_BGT6gSf8fV2Ph4JWalrsNCDZSjLbOdRwCnz3I4,1540
4
+ aiteamutils/base_service.py,sha256=CfNBAgFREqOC1791ex2plKLZiSZ1U5el4GvvkVsN4qU,2901
5
5
  aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
6
  aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
- aiteamutils/database.py,sha256=MtmrX_pDzKFQM-P3OAfm2mALvhRg-v5JWtGBoiegeU0,1484
7
+ aiteamutils/database.py,sha256=CbX7eNFwqz9O4ywVQMLlLrb6hDUJPtsgGg_Hgef-p2I,8126
8
8
  aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
9
9
  aiteamutils/exceptions.py,sha256=_lKWXq_ujNj41xN6LDE149PwsecAP7lgYWbOBbLOntg,15368
10
10
  aiteamutils/security.py,sha256=xFVrjttxwXB1TTjqgRQQgQJQohQBT28vuW8FVLjvi-M,10103
11
11
  aiteamutils/validators.py,sha256=PvI9hbMEAqTawgxPbiWRyx2r9yTUrpNBQs1AD3w4F2U,7726
12
- aiteamutils/version.py,sha256=Ab2-CQIQ45ZW4Pc2j-7gD_cs2b9qIq_4inLBWtqYLgU,42
13
- aiteamutils-0.2.64.dist-info/METADATA,sha256=EQlA6VzdmjttNL8PgZftxQevln_ltjDkD7wbiZ6MRvs,1718
14
- aiteamutils-0.2.64.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- aiteamutils-0.2.64.dist-info/RECORD,,
12
+ aiteamutils/version.py,sha256=sLTec9HL2qseXAi6mzbn7T-ZwoQOncK22kiRkfVSwH8,42
13
+ aiteamutils-0.2.66.dist-info/METADATA,sha256=SkG0OhfTLO7TiWa1LtvRjPnBNYPqULCb8sb6PllJ21s,1718
14
+ aiteamutils-0.2.66.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ aiteamutils-0.2.66.dist-info/RECORD,,