aiteamutils 0.2.75__py3-none-any.whl → 0.2.76__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
aiteamutils/base_model.py CHANGED
@@ -1,4 +1,4 @@
1
- from datetime import datetime, timezone
1
+ from datetime import datetime, timedelta, timezone
2
2
  from typing import Any, Dict, TypeVar, Generic, Optional
3
3
  from ulid import ULID
4
4
  from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -24,16 +24,17 @@ class BaseColumn(Base):
24
24
  nullable=False
25
25
  )
26
26
  created_at: Mapped[datetime] = mapped_column(
27
- default=datetime.utcnow,
27
+ default=datetime.now(timezone.utc),
28
28
  index=True
29
29
  )
30
30
  updated_at: Mapped[datetime] = mapped_column(
31
- default=datetime.utcnow,
32
- onupdate=datetime.utcnow,
31
+ default=datetime.now(timezone.utc),
32
+ onupdate=datetime.now(timezone.utc),
33
33
  index=True
34
34
  )
35
35
  deleted_at: Mapped[datetime] = mapped_column(
36
36
  default=None,
37
+ nullable=True
37
38
  )
38
39
  is_deleted: Mapped[bool] = mapped_column(
39
40
  default=False,
@@ -39,12 +39,17 @@ class BaseRepository(Generic[ModelType]):
39
39
  #######################
40
40
  # 입력 및 수정, 삭제 #
41
41
  #######################
42
- async def create(self, entity_data: Dict[str, Any]) -> ModelType:
42
+ async def create(
43
+ self,
44
+ entity_data: Dict[str, Any],
45
+ exclude_entities: List[str] | None = None
46
+ ) -> ModelType:
43
47
  try:
44
48
  return await create_entity(
45
49
  session=self.session,
46
50
  model=self.model,
47
- entity_data=entity_data
51
+ entity_data=entity_data,
52
+ exclude_entities=exclude_entities
48
53
  )
49
54
  except CustomException as e:
50
55
  raise e
@@ -46,21 +46,19 @@ class BaseService(Generic[ModelType]):
46
46
  ) -> ModelType:
47
47
 
48
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}
49
+ async with self.db_session.begin():
50
+ # 고유 검사 수행
51
+ if unique_check:
52
+ await validate_unique_fields(self.db_session, unique_check, find_value=True)
53
+ # 외래 키 검사 수행
54
+ if fk_check:
55
+ await validate_unique_fields(self.db_session, fk_check, find_value=False)
61
56
 
62
- # repository의 create 메서드를 트랜잭션 내에서 실행
63
- return await self.repository.create(entity_data)
57
+ # repository의 create 메서드를 트랜잭션 내에서 실행
58
+ return await self.repository.create(
59
+ entity_data=entity_data,
60
+ exclude_entities=exclude_entities
61
+ )
64
62
  except CustomException as e:
65
63
  raise e
66
64
  except Exception as e:
aiteamutils/database.py CHANGED
@@ -23,7 +23,6 @@ from sqlalchemy import MetaData, Table, insert
23
23
  #패키지 라이브러리
24
24
  from .exceptions import ErrorCode, CustomException
25
25
 
26
-
27
26
  ModelType = TypeVar("ModelType", bound=DeclarativeBase)
28
27
 
29
28
  ##################
@@ -32,8 +31,11 @@ ModelType = TypeVar("ModelType", bound=DeclarativeBase)
32
31
  def process_entity_data(
33
32
  model: Type[ModelType],
34
33
  entity_data: Dict[str, Any],
35
- existing_data: Dict[str, Any] = None
34
+ existing_data: Dict[str, Any] = None,
35
+ exclude_entities: List[str] | None = None
36
36
  ) -> Dict[str, Any]:
37
+ from .security import hash_password
38
+
37
39
  """
38
40
  엔티티 데이터를 전처리하고 모델 속성과 extra_data를 분리합니다.
39
41
 
@@ -64,6 +66,9 @@ def process_entity_data(
64
66
  if existing_data and "extra_data" in existing_data:
65
67
  extra_data = existing_data["extra_data"].copy()
66
68
 
69
+ # 제외할 엔티티가 있으면 제거
70
+ if exclude_entities:
71
+ entity_data = {k: v for k, v in entity_data.items() if k not in exclude_entities}
67
72
 
68
73
  # Swagger 자동 생성 속성 패턴
69
74
  swagger_patterns = {"additionalProp1", "additionalProp2", "additionalProp3"}
@@ -73,9 +78,14 @@ def process_entity_data(
73
78
  if key in swagger_patterns:
74
79
  continue
75
80
 
81
+ # 패스워드 필드 처리
82
+ if key == "password":
83
+ value = hash_password(value) # 비밀번호 암호화
84
+
76
85
  # 모델 속성에 있는 경우 model_data에 추가
77
86
  if key in model_attr:
78
87
  model_data[key] = value
88
+
79
89
  # 모델 속성에 없는 경우 extra_data에 추가
80
90
  else:
81
91
  extra_data[key] = value
@@ -237,7 +247,8 @@ def build_conditions(
237
247
  async def create_entity(
238
248
  session: AsyncSession,
239
249
  model: Type[ModelType],
240
- entity_data: Dict[str, Any]
250
+ entity_data: Dict[str, Any],
251
+ exclude_entities: List[str] | None = None
241
252
  ) -> ModelType:
242
253
  """
243
254
  새로운 엔티티를 데이터베이스에 생성합니다.
@@ -255,9 +266,11 @@ async def create_entity(
255
266
  """
256
267
  try:
257
268
  # 엔티티 데이터 전처리
258
- processed_data = process_entity_data(model, entity_data)
259
-
260
- # 외래 키 필드 검증
269
+ processed_data = process_entity_data(
270
+ model=model,
271
+ entity_data=entity_data,
272
+ exclude_entities=exclude_entities
273
+ )
261
274
 
262
275
  # 새로운 엔티티 생성
263
276
  entity = model(**processed_data)
@@ -267,7 +280,6 @@ async def create_entity(
267
280
 
268
281
  # 데이터베이스에 커밋
269
282
  await session.flush()
270
- await session.commit()
271
283
  await session.refresh(entity)
272
284
 
273
285
  # 생성된 엔티티 반환
@@ -336,7 +348,6 @@ async def update_entity(
336
348
 
337
349
  # 변경 사항 커밋
338
350
  await session.flush()
339
- await session.commit()
340
351
  await session.refresh(entity)
341
352
 
342
353
  return entity
@@ -373,7 +384,6 @@ async def delete_entity(
373
384
  entity.deleted_at = datetime.now()
374
385
 
375
386
  await session.flush()
376
- await session.commit()
377
387
  await session.refresh(entity)
378
388
 
379
389
  except SQLAlchemyError as e:
@@ -498,33 +508,25 @@ async def get_entity(
498
508
  ##################
499
509
  async def log_create(
500
510
  session: AsyncSession,
501
- table_name: str,
511
+ model: str,
502
512
  log_data: Dict[str, Any],
503
- request: Request = None
513
+ request: Optional[Request] = None
504
514
  ) -> None:
505
515
  try:
506
- # ULID 생성
507
- if "ulid" not in log_data:
508
- log_data["ulid"] = ULID()
509
-
510
516
  # 사용자 에이전트 및 IP 주소 추가
511
517
  if request:
512
518
  log_data["user_agent"] = request.headers.get("user-agent")
513
519
  log_data["ip_address"] = request.headers.get("x-forwarded-for") or request.client.host
514
520
 
515
- # 동적으로 테이블 로드
516
- metadata = MetaData(bind=session.bind)
517
- table = Table(table_name, metadata, autoload_with=session.bind)
518
-
519
- # 데이터 삽입
520
- insert_stmt = insert(table).values(log_data)
521
- await session.execute(insert_stmt)
522
- await session.commit()
523
-
521
+ await create_entity(
522
+ session=session,
523
+ model=model,
524
+ entity_data=log_data
525
+ )
524
526
  except Exception as e:
525
527
  raise CustomException(
526
528
  ErrorCode.INTERNAL_ERROR,
527
- detail=f"{model.__name__}|{str(e)}",
529
+ detail=f"{model}|{str(e)}",
528
530
  source_function="database.log_create",
529
531
  original_error=e
530
532
  )
aiteamutils/security.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """보안 관련 유틸리티."""
2
- from datetime import datetime, timedelta, UTC
2
+ from datetime import datetime, timedelta, timezone
3
3
  from typing import Dict, Any, Optional, Literal, Callable, TYPE_CHECKING
4
4
  from fastapi import Request, HTTPException, status
5
5
  from functools import wraps
@@ -85,7 +85,7 @@ def rate_limit(
85
85
  rate_limit_key = f"rate_limit:{client_ip}:{func.__name__}"
86
86
 
87
87
  try:
88
- now = datetime.now(UTC)
88
+ now = datetime.now(timezone.utc)
89
89
 
90
90
  # 현재 rate limit 정보 가져오기
91
91
  rate_info = _rate_limits.get(rate_limit_key)
@@ -136,54 +136,54 @@ async def create_jwt_token(
136
136
  user_data: Dict[str, Any],
137
137
  token_type: Literal["access", "refresh"],
138
138
  db_session: AsyncSession,
139
- token_settings: Dict[str, str],
140
- request: Optional[Request] = None,
139
+ token_settings: Dict[str, Any],
141
140
  ) -> str:
142
141
  """JWT 토큰을 생성하고 로그를 기록합니다."""
143
142
  try:
144
143
  # 토큰 데이터 구성
145
144
  if token_type == "access":
146
- expires_at = datetime.now(UTC) + timedelta(minutes=token_settings.ACCESS_TOKEN_EXPIRE_MINUTES)
145
+ expires_at = datetime.now(timezone.utc) + timedelta(minutes=token_settings["ACCESS_TOKEN_EXPIRE_MINUTES"])
146
+
147
147
  token_data = {
148
148
  # 등록 클레임
149
- "iss": token_settings.TOKEN_ISSUER,
150
- "sub": user_data["username"],
151
- "aud": token_settings.TOKEN_AUDIENCE,
149
+ "iss": token_settings["TOKEN_ISSUER"],
150
+ "sub": user_data.username,
151
+ "aud": token_settings["TOKEN_AUDIENCE"],
152
152
  "exp": expires_at,
153
153
 
154
154
  # 공개 클레임
155
- "username": user_data["username"],
156
- "name": user_data.get("name"),
155
+ "username": user_data.username,
156
+ "name": user_data.name,
157
157
 
158
158
  # 비공개 클레임
159
- "user_ulid": user_data["ulid"],
160
- "role_ulid": user_data.get("role_ulid"),
161
- "status": user_data.get("status"),
162
- "last_login": datetime.now(UTC).isoformat(),
159
+ "user_ulid": user_data.ulid,
160
+ "role_ulid": user_data.role_ulid,
161
+ "status": user_data.status,
162
+ "last_login": datetime.now(timezone.utc).isoformat(),
163
163
  "token_type": token_type,
164
164
 
165
165
  # 조직 관련 클레임
166
- "organization_ulid": user_data.get("organization_ulid"),
167
- "organization_id": user_data.get("organization_id"),
168
- "organization_name": user_data.get("organization_name"),
169
- "company_name": user_data.get("company_name")
166
+ "organization_ulid": user_data.role.organization.ulid,
167
+ "organization_id": user_data.role.organization.id,
168
+ "organization_name": user_data.role.organization.name,
169
+ "company_name": user_data.role.organization.company.name
170
170
  }
171
171
  else: # refresh token
172
- expires_at = datetime.now(UTC) + timedelta(days=14)
172
+ expires_at = datetime.now(timezone.utc) + timedelta(days=14)
173
173
  token_data = {
174
- "iss": token_settings.TOKEN_ISSUER,
175
- "sub": user_data["username"],
174
+ "iss": token_settings["TOKEN_ISSUER"],
175
+ "sub": user_data.username,
176
176
  "exp": expires_at,
177
177
  "token_type": token_type,
178
- "user_ulid": user_data["ulid"]
178
+ "user_ulid": user_data.ulid
179
179
  }
180
180
 
181
181
  # JWT 토큰 생성
182
182
  try:
183
183
  token = jwt.encode(
184
184
  token_data,
185
- token_settings.JWT_SECRET,
186
- algorithm=token_settings.JWT_ALGORITHM
185
+ token_settings["JWT_SECRET"],
186
+ algorithm=token_settings["JWT_ALGORITHM"]
187
187
  )
188
188
  except Exception as e:
189
189
  raise CustomException(
@@ -192,26 +192,6 @@ async def create_jwt_token(
192
192
  source_function="security.create_jwt_token",
193
193
  original_error=e
194
194
  )
195
-
196
- # 로그 생성
197
- try:
198
- log_data = {
199
- "type": ActivityType.ACCESS_TOKEN_ISSUED if token_type == "access" else ActivityType.REFRESH_TOKEN_ISSUED,
200
- "user_ulid": user_data["ulid"],
201
- "token": token
202
- }
203
-
204
- # log_create 함수 호출
205
- await log_create(
206
- session=db_session,
207
- table_name="user_logs",
208
- log_data=log_data,
209
- request=request
210
- )
211
- except Exception as e:
212
- # 로그 생성 실패는 토큰 생성에 영향을 주지 않음
213
- logging.error(f"Failed to create token log: {str(e)}")
214
-
215
195
  return token
216
196
 
217
197
  except CustomException as e:
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.75"
2
+ __version__ = "0.2.76"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.75
3
+ Version: 0.2.76
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
@@ -0,0 +1,15 @@
1
+ aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
+ aiteamutils/base_model.py,sha256=SWifZPzlUGyKAjmV7Uoe7PUhedm215efD7zOay8DhNc,2906
3
+ aiteamutils/base_repository.py,sha256=VLCLLVkNnYLmZ6EMcr3Tu0RkMqDyMhtCO3M_2j6q-R8,4409
4
+ aiteamutils/base_service.py,sha256=GmO_fqrSEbIs_Jc5BoRBTLfaJaS6CIxYPAs4B4TdtaU,9123
5
+ aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
+ aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
+ aiteamutils/database.py,sha256=zf5Ab5ZFYQBrtvxW9te9RoVsWlMkOpTdmJ8rDwfvINs,19192
8
+ aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
9
+ aiteamutils/exceptions.py,sha256=3FUCIqXgYmMqonnMgUlh-J2xtApiiCgg4WM-2UV4vmQ,15823
10
+ aiteamutils/security.py,sha256=xjuSTwn3_DeLHqUb17GwDU58nH7wDyk_7Wq8CPskXo8,9579
11
+ aiteamutils/validators.py,sha256=PvI9hbMEAqTawgxPbiWRyx2r9yTUrpNBQs1AD3w4F2U,7726
12
+ aiteamutils/version.py,sha256=sAzd6RPEDU8drCBs0LCLhJkj0zQgL2FdPYnX-PVpUrs,42
13
+ aiteamutils-0.2.76.dist-info/METADATA,sha256=EVxAPwXRwIYNWzHV8ekVUIKXKlyY_RogJn1wWK5fE58,1718
14
+ aiteamutils-0.2.76.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ aiteamutils-0.2.76.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
- aiteamutils/base_model.py,sha256=TJiiA7bFhePObPJEzF1Qz4ekPfFoIxKGpe848BBdHLc,2840
3
- aiteamutils/base_repository.py,sha256=oTZP3mrkqZwAGv8cNqi0tp9wellvTuO6EXN1P1o7zRs,4285
4
- aiteamutils/base_service.py,sha256=tkGGVN8wrx9dmCVVnRSsoYGPmVD2H7NmEz-PoifIvW0,9300
5
- aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
- aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
- aiteamutils/database.py,sha256=8T8c7RpcPEcBXO-AU_-1UhnTI4RwkPLxWF0eWQYwnME,19080
8
- aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
9
- aiteamutils/exceptions.py,sha256=3FUCIqXgYmMqonnMgUlh-J2xtApiiCgg4WM-2UV4vmQ,15823
10
- aiteamutils/security.py,sha256=7cKY6XhiSEkQJe-XYE2tyYc2u1JLYeXUEjg7AUzEnQY,10277
11
- aiteamutils/validators.py,sha256=PvI9hbMEAqTawgxPbiWRyx2r9yTUrpNBQs1AD3w4F2U,7726
12
- aiteamutils/version.py,sha256=FgfB6O4LkLSpldQbG5FG5Fai_x5fo9cg9AQ85w_3RF8,42
13
- aiteamutils-0.2.75.dist-info/METADATA,sha256=QtxRHW3XcGt5NndEB95Fue80VRGgM54zWbLtssOtkcI,1718
14
- aiteamutils-0.2.75.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- aiteamutils-0.2.75.dist-info/RECORD,,