pyjallib 0.1.17__py3-none-any.whl → 0.1.20__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.
pyjallib/perforce.py CHANGED
@@ -11,6 +11,7 @@ P4Python을 사용하는 Perforce 모듈.
11
11
 
12
12
  from P4 import P4, P4Exception
13
13
  import os
14
+ from .exceptions import PerforceError, ValidationError
14
15
 
15
16
 
16
17
  class Perforce:
@@ -21,8 +22,6 @@ class Perforce:
21
22
  self.p4 = P4()
22
23
  self.connected = False
23
24
  self.workspaceRoot = r""
24
- self.last_error = None
25
- self.last_warnings = []
26
25
 
27
26
  def _is_connected(self) -> bool:
28
27
  """Perforce 서버 연결 상태를 확인합니다.
@@ -31,40 +30,18 @@ class Perforce:
31
30
  bool: 연결되어 있으면 True, 아니면 False
32
31
  """
33
32
  if not self.connected:
34
- self.last_error = "Perforce 서버에 연결되지 않았습니다."
35
33
  return False
36
34
  return True
37
-
38
- def _handle_p4_exception(self, e: P4Exception, context_msg: str = "") -> None:
39
- """P4Exception을 처리하고 에러 정보를 저장합니다.
40
-
41
- Args:
42
- e (P4Exception): 발생한 예외
43
- context_msg (str, optional): 예외가 발생한 컨텍스트 설명
44
- """
45
- self.last_error = f"{context_msg} 중 P4Exception 발생: {e}"
46
- self.last_warnings = list(self.p4.warnings) if self.p4.warnings else []
47
-
48
- def get_last_error(self) -> str:
49
- """마지막 에러 메시지를 반환합니다.
50
-
51
- Returns:
52
- str: 마지막 에러 메시지. 에러가 없으면 None
53
- """
54
- return self.last_error
55
-
56
- def get_last_warnings(self) -> list:
57
- """마지막 경고 메시지들을 반환합니다.
35
+
36
+ def _ensure_connected(self) -> None:
37
+ """Perforce 서버 연결 상태를 확인하고 연결되지 않은 경우 예외를 발생시킵니다.
58
38
 
59
- Returns:
60
- list: 마지막 경고 메시지들의 리스트
39
+ Raises:
40
+ PerforceError: 연결되지 않은 상태인 경우
61
41
  """
62
- return self.last_warnings
63
-
64
- def clear_last_error(self) -> None:
65
- """마지막 에러와 경고 정보를 초기화합니다."""
66
- self.last_error = None
67
- self.last_warnings = []
42
+ if not self.connected:
43
+ error_message = "Perforce 서버에 연결되지 않았습니다."
44
+ raise PerforceError(error_message)
68
45
 
69
46
  def connect(self, workspace_name: str) -> bool:
70
47
  """지정된 워크스페이스에 연결합니다.
@@ -75,7 +52,6 @@ class Perforce:
75
52
  Returns:
76
53
  bool: 연결 성공 시 True, 실패 시 False
77
54
  """
78
- self.clear_last_error()
79
55
  try:
80
56
  self.p4.client = workspace_name
81
57
  self.p4.connect()
@@ -90,15 +66,14 @@ class Perforce:
90
66
  root_path = os.path.normpath(root_path)
91
67
 
92
68
  self.workspaceRoot = root_path
93
- except (IndexError, KeyError) as e:
94
- self.last_error = f"워크스페이스 루트 경로 가져오기 실패: {e}"
69
+ except (IndexError, KeyError):
95
70
  self.workspaceRoot = ""
96
71
 
97
72
  return True
98
73
  except P4Exception as e:
99
74
  self.connected = False
100
- self._handle_p4_exception(e, f"'{workspace_name}' 워크스페이스 연결 실패")
101
- return False
75
+ error_message = f"'{workspace_name}' 워크스페이스 연결 실패 중 P4Exception 발생: {e}"
76
+ raise PerforceError(error_message)
102
77
 
103
78
  def get_pending_change_list(self) -> list:
104
79
  """워크스페이스의 Pending된 체인지 리스트를 가져옵니다.
@@ -106,9 +81,7 @@ class Perforce:
106
81
  Returns:
107
82
  list: 체인지 리스트 정보 딕셔너리들의 리스트
108
83
  """
109
- if not self._is_connected():
110
- return []
111
- self.clear_last_error()
84
+ self._ensure_connected()
112
85
  try:
113
86
  pending_changes = self.p4.run_changes("-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
114
87
  change_numbers = [int(cl['change']) for cl in pending_changes]
@@ -122,8 +95,8 @@ class Perforce:
122
95
 
123
96
  return change_list_info
124
97
  except P4Exception as e:
125
- self._handle_p4_exception(e, "Pending 체인지 리스트 조회 실패")
126
- return []
98
+ error_message = f"Pending 체인지 리스트 조회 실패 중 P4Exception 발생: {e}"
99
+ raise PerforceError(error_message)
127
100
 
128
101
  def create_change_list(self, description: str) -> dict:
129
102
  """새로운 체인지 리스트를 생성합니다.
@@ -134,9 +107,7 @@ class Perforce:
134
107
  Returns:
135
108
  dict: 생성된 체인지 리스트 정보. 실패 시 빈 딕셔너리
136
109
  """
137
- if not self._is_connected():
138
- return {}
139
- self.clear_last_error()
110
+ self._ensure_connected()
140
111
  try:
141
112
  change_spec = self.p4.fetch_change()
142
113
  change_spec["Description"] = description
@@ -144,11 +115,11 @@ class Perforce:
144
115
  created_change_number = int(result[0].split()[1])
145
116
  return self.get_change_list_by_number(created_change_number)
146
117
  except P4Exception as e:
147
- self._handle_p4_exception(e, f"체인지 리스트 생성 실패 ('{description}')")
148
- return {}
118
+ error_message = f"체인지 리스트 생성 실패 ('{description}') 중 P4Exception 발생: {e}"
119
+ raise PerforceError(error_message)
149
120
  except (IndexError, ValueError) as e:
150
- self.last_error = f"체인지 리스트 번호 파싱 오류: {e}"
151
- return {}
121
+ error_message = f"체인지 리스트 번호 파싱 오류: {e}"
122
+ raise PerforceError(error_message)
152
123
 
153
124
  def get_change_list_by_number(self, change_list_number: int) -> dict:
154
125
  """체인지 리스트 번호로 체인지 리스트를 가져옵니다.
@@ -159,19 +130,17 @@ class Perforce:
159
130
  Returns:
160
131
  dict: 체인지 리스트 정보. 실패 시 빈 딕셔너리
161
132
  """
162
- if not self._is_connected():
163
- return {}
164
- self.clear_last_error()
133
+ self._ensure_connected()
165
134
  try:
166
135
  cl_info = self.p4.fetch_change(change_list_number)
167
136
  if cl_info:
168
137
  return cl_info
169
138
  else:
170
- self.last_error = f"체인지 리스트 {change_list_number}를 찾을 수 없습니다."
171
- return {}
139
+ error_message = f"체인지 리스트 {change_list_number}를 찾을 수 없습니다."
140
+ raise PerforceError(error_message)
172
141
  except P4Exception as e:
173
- self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 정보 조회 실패")
174
- return {}
142
+ error_message = f"체인지 리스트 {change_list_number} 정보 조회 실패 중 P4Exception 발생: {e}"
143
+ raise PerforceError(error_message)
175
144
 
176
145
  def get_change_list_by_description(self, description: str) -> dict:
177
146
  """체인지 리스트 설명으로 체인지 리스트를 가져옵니다.
@@ -182,9 +151,7 @@ class Perforce:
182
151
  Returns:
183
152
  dict: 체인지 리스트 정보 (일치하는 첫 번째 체인지 리스트)
184
153
  """
185
- if not self._is_connected():
186
- return {}
187
- self.clear_last_error()
154
+ self._ensure_connected()
188
155
  try:
189
156
  pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
190
157
  for cl in pending_changes:
@@ -193,8 +160,8 @@ class Perforce:
193
160
  return self.get_change_list_by_number(int(cl['change']))
194
161
  return {}
195
162
  except P4Exception as e:
196
- self._handle_p4_exception(e, f"설명으로 체인지 리스트 조회 실패 ('{description}')")
197
- return {}
163
+ error_message = f"설명으로 체인지 리스트 조회 실패 ('{description}') 중 P4Exception 발생: {e}"
164
+ raise PerforceError(error_message)
198
165
 
199
166
  def get_change_list_by_description_pattern(self, description_pattern: str, exact_match: bool = False) -> list:
200
167
  """설명 패턴과 일치하는 Pending 체인지 리스트들을 가져옵니다.
@@ -207,10 +174,7 @@ class Perforce:
207
174
  Returns:
208
175
  list: 패턴과 일치하는 체인지 리스트 정보들의 리스트
209
176
  """
210
- if not self._is_connected():
211
- return []
212
-
213
- self.clear_last_error()
177
+ self._ensure_connected()
214
178
  try:
215
179
  pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
216
180
  matching_changes = []
@@ -235,8 +199,8 @@ class Perforce:
235
199
 
236
200
  return matching_changes
237
201
  except P4Exception as e:
238
- self._handle_p4_exception(e, f"설명 패턴으로 체인지 리스트 조회 실패 ('{description_pattern}')")
239
- return []
202
+ error_message = f"설명 패턴으로 체인지 리스트 조회 실패 ('{description_pattern}') 중 P4Exception 발생: {e}"
203
+ raise PerforceError(error_message)
240
204
 
241
205
  def disconnect(self):
242
206
  """Perforce 서버와의 연결을 해제합니다."""
@@ -245,7 +209,8 @@ class Perforce:
245
209
  self.p4.disconnect()
246
210
  self.connected = False
247
211
  except P4Exception as e:
248
- self._handle_p4_exception(e, "Perforce 서버 연결 해제")
212
+ error_message = f"Perforce 서버 연결 해제 중 P4Exception 발생: {e}"
213
+ raise PerforceError(error_message)
249
214
 
250
215
  def __del__(self):
251
216
  """객체가 소멸될 때 자동으로 연결을 해제합니다."""
@@ -269,18 +234,15 @@ class Perforce:
269
234
  }
270
235
  }
271
236
  """
272
- if not self._is_connected():
273
- return {}
237
+ self._ensure_connected()
274
238
  if not file_paths:
275
239
  return {}
276
240
 
277
241
  # 타입 검증: 리스트가 아닌 경우 에러 발생
278
242
  if not isinstance(file_paths, list):
279
243
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out() 메서드를 사용하세요."
280
- self.last_error = error_msg
281
- raise TypeError(error_msg)
244
+ raise ValidationError(error_msg)
282
245
 
283
- self.clear_last_error()
284
246
  result = {}
285
247
  try:
286
248
  # 각 파일의 상태 확인
@@ -310,15 +272,16 @@ class Perforce:
310
272
  # 파일이 perforce에 없거나 접근할 수 없는 경우
311
273
  if not any("not opened" in err.lower() or "no such file" in err.lower()
312
274
  for err in self.p4.errors):
313
- self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인")
275
+ error_message = f"파일 '{file_path}' 체크아웃 상태 확인 중 P4Exception 발생: {e}"
276
+ raise PerforceError(error_message)
314
277
 
315
278
  result[file_path] = file_status
316
279
 
317
280
  return result
318
281
 
319
282
  except P4Exception as e:
320
- self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 ({file_paths})")
321
- return {}
283
+ error_message = f"파일들 체크아웃 상태 확인 ({file_paths}) 중 P4Exception 발생: {e}"
284
+ raise PerforceError(error_message)
322
285
 
323
286
  def is_file_checked_out(self, file_path: str) -> bool:
324
287
  """단일 파일의 체크아웃 상태를 간단히 확인합니다.
@@ -342,10 +305,7 @@ class Perforce:
342
305
  Returns:
343
306
  bool: 파일이 해당 체인지 리스트에 있으면 True, 아니면 False
344
307
  """
345
- if not self._is_connected():
346
- return False
347
-
348
- self.clear_last_error()
308
+ self._ensure_connected()
349
309
  try:
350
310
  # 해당 체인지 리스트의 파일들 가져오기
351
311
  opened_files = self.p4.run_opened("-c", change_list_number)
@@ -363,8 +323,8 @@ class Perforce:
363
323
  return False
364
324
 
365
325
  except P4Exception as e:
366
- self._handle_p4_exception(e, f"파일 '{file_path}' 체인지 리스트 {change_list_number} 포함 여부 확인")
367
- return False
326
+ error_message = f"파일 '{file_path}' 체인지 리스트 {change_list_number} 포함 여부 확인 중 P4Exception 발생: {e}"
327
+ raise PerforceError(error_message)
368
328
 
369
329
  def edit_change_list(self, change_list_number: int, description: str = None, add_file_paths: list = None, remove_file_paths: list = None) -> dict:
370
330
  """체인지 리스트를 편집합니다.
@@ -378,9 +338,7 @@ class Perforce:
378
338
  Returns:
379
339
  dict: 업데이트된 체인지 리스트 정보
380
340
  """
381
- if not self._is_connected():
382
- return {}
383
- self.clear_last_error()
341
+ self._ensure_connected()
384
342
  try:
385
343
  if description is not None:
386
344
  change_spec = self.p4.fetch_change(change_list_number)
@@ -394,20 +352,22 @@ class Perforce:
394
352
  try:
395
353
  self.p4.run_reopen("-c", change_list_number, file_path)
396
354
  except P4Exception as e_reopen:
397
- self._handle_p4_exception(e_reopen, f"파일 '{file_path}'을 CL {change_list_number}로 이동")
355
+ error_message = f"파일 '{file_path}'을 CL {change_list_number}로 이동 중 P4Exception 발생: {e_reopen}"
356
+ raise PerforceError(error_message)
398
357
 
399
358
  if remove_file_paths:
400
359
  for file_path in remove_file_paths:
401
360
  try:
402
361
  self.p4.run_revert("-c", change_list_number, file_path)
403
362
  except P4Exception as e_revert:
404
- self._handle_p4_exception(e_revert, f"파일 '{file_path}'을 CL {change_list_number}에서 제거(revert)")
363
+ error_message = f"파일 '{file_path}'을 CL {change_list_number}에서 제거(revert) 중 P4Exception 발생: {e_revert}"
364
+ raise PerforceError(error_message)
405
365
 
406
366
  return self.get_change_list_by_number(change_list_number)
407
367
 
408
368
  except P4Exception as e:
409
- self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 편집")
410
- return self.get_change_list_by_number(change_list_number)
369
+ error_message = f"체인지 리스트 {change_list_number} 편집 중 P4Exception 발생: {e}"
370
+ raise PerforceError(error_message)
411
371
 
412
372
  def _file_op(self, command: str, file_path: str, change_list_number: int, op_name: str) -> bool:
413
373
  """파일 작업을 수행하는 내부 헬퍼 함수입니다.
@@ -421,9 +381,7 @@ class Perforce:
421
381
  Returns:
422
382
  bool: 작업 성공 시 True, 실패 시 False
423
383
  """
424
- if not self._is_connected():
425
- return False
426
- self.clear_last_error()
384
+ self._ensure_connected()
427
385
  try:
428
386
  if command == "edit":
429
387
  self.p4.run_edit("-c", change_list_number, file_path)
@@ -432,12 +390,12 @@ class Perforce:
432
390
  elif command == "delete":
433
391
  self.p4.run_delete("-c", change_list_number, file_path)
434
392
  else:
435
- self.last_error = f"지원되지 않는 파일 작업: {command}"
436
- return False
393
+ error_message = f"지원되지 않는 파일 작업: {command}"
394
+ raise ValidationError(error_message)
437
395
  return True
438
396
  except P4Exception as e:
439
- self._handle_p4_exception(e, f"파일 '{file_path}' {op_name} (CL: {change_list_number})")
440
- return False
397
+ error_message = f"파일 '{file_path}' {op_name} (CL: {change_list_number}) 중 P4Exception 발생: {e}"
398
+ raise PerforceError(error_message)
441
399
 
442
400
  def checkout_file(self, file_path: str, change_list_number: int) -> bool:
443
401
  """파일을 체크아웃합니다.
@@ -467,8 +425,7 @@ class Perforce:
467
425
  # 타입 검증: 리스트가 아닌 경우 에러 발생
468
426
  if not isinstance(file_paths, list):
469
427
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 checkout_file() 메서드를 사용하세요."
470
- self.last_error = error_msg
471
- raise TypeError(error_msg)
428
+ raise ValidationError(error_msg)
472
429
 
473
430
  all_success = True
474
431
  for file_path in file_paths:
@@ -506,8 +463,7 @@ class Perforce:
506
463
  # 타입 검증: 리스트가 아닌 경우 에러 발생
507
464
  if not isinstance(file_paths, list):
508
465
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 add_file() 메서드를 사용하세요."
509
- self.last_error = error_msg
510
- raise TypeError(error_msg)
466
+ raise ValidationError(error_msg)
511
467
 
512
468
  all_success = True
513
469
  for file_path in file_paths:
@@ -545,8 +501,7 @@ class Perforce:
545
501
  # 타입 검증: 리스트가 아닌 경우 에러 발생
546
502
  if not isinstance(file_paths, list):
547
503
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 delete_file() 메서드를 사용하세요."
548
- self.last_error = error_msg
549
- raise TypeError(error_msg)
504
+ raise ValidationError(error_msg)
550
505
 
551
506
  all_success = True
552
507
  for file_path in file_paths:
@@ -567,21 +522,20 @@ class Perforce:
567
522
  Returns:
568
523
  bool: 제출 성공 시 True, 실패 시 False
569
524
  """
570
- if not self._is_connected():
571
- return False
572
- self.clear_last_error()
525
+ self._ensure_connected()
573
526
 
574
527
  submit_success = False
575
528
  try:
576
529
  self.p4.run_submit("-c", change_list_number)
577
530
  submit_success = True
578
531
  except P4Exception as e:
579
- self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 제출 실패")
580
532
  if any("nothing to submit" in err.lower() for err in self.p4.errors):
581
- self.last_error = f"체인지 리스트 {change_list_number}에 제출할 파일이 없습니다."
582
- submit_success = False
533
+ error_message = f"체인지 리스트 {change_list_number}에 제출할 파일이 없습니다."
534
+ else:
535
+ error_message = f"체인지 리스트 {change_list_number} 제출 실패 중 P4Exception 발생: {e}"
536
+ raise PerforceError(error_message)
583
537
 
584
- # 제출 성공 여부와 관계없이 후속 작업 실행
538
+ # 제출 성공 후속 작업 실행
585
539
  try:
586
540
  # 제출 후 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트
587
541
  if auto_revert_unchanged:
@@ -591,8 +545,8 @@ class Perforce:
591
545
  # 빈 체인지 리스트 삭제
592
546
  self.delete_empty_change_list(change_list_number)
593
547
  except Exception as e:
594
- if not self.last_error: # 기존 에러가 없는 경우만 설정
595
- self.last_error = f"체인지 리스트 {change_list_number} 제출 후 후속 작업 중 오류 발생: {e}"
548
+ error_message = f"체인지 리스트 {change_list_number} 제출 후속 작업 중 오류 발생: {e}"
549
+ raise PerforceError(error_message)
596
550
 
597
551
  return submit_success
598
552
 
@@ -686,9 +640,7 @@ class Perforce:
686
640
  Returns:
687
641
  bool: 되돌리기 및 삭제 성공 시 True, 실패 시 False
688
642
  """
689
- if not self._is_connected():
690
- return False
691
- self.clear_last_error()
643
+ self._ensure_connected()
692
644
  try:
693
645
  # 체인지 리스트의 모든 파일 되돌리기
694
646
  self.p4.run_revert("-c", change_list_number, "//...")
@@ -697,13 +649,13 @@ class Perforce:
697
649
  try:
698
650
  self.p4.run_change("-d", change_list_number)
699
651
  except P4Exception as e_delete:
700
- self._handle_p4_exception(e_delete, f"체인지 리스트 {change_list_number} 삭제")
701
- return False
652
+ error_message = f"체인지 리스트 {change_list_number} 삭제 중 P4Exception 발생: {e_delete}"
653
+ raise PerforceError(error_message)
702
654
 
703
655
  return True
704
656
  except P4Exception as e:
705
- self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 전체 되돌리기 실패")
706
- return False
657
+ error_message = f"체인지 리스트 {change_list_number} 전체 되돌리기 실패 중 P4Exception 발생: {e}"
658
+ raise PerforceError(error_message)
707
659
 
708
660
  def delete_empty_change_list(self, change_list_number: int) -> bool:
709
661
  """빈 체인지 리스트를 삭제합니다.
@@ -714,25 +666,22 @@ class Perforce:
714
666
  Returns:
715
667
  bool: 삭제 성공 시 True, 실패 시 False
716
668
  """
717
- if not self._is_connected():
718
- return False
719
-
720
- self.clear_last_error()
669
+ self._ensure_connected()
721
670
  try:
722
671
  # 체인지 리스트 정보 가져오기
723
672
  change_spec = self.p4.fetch_change(change_list_number)
724
673
 
725
674
  # 파일이 있는지 확인
726
675
  if change_spec and change_spec.get('Files') and len(change_spec['Files']) > 0:
727
- self.last_error = f"체인지 리스트 {change_list_number}에 파일이 {len(change_spec['Files'])}개 있어 삭제할 수 없습니다."
728
- return False
676
+ error_message = f"체인지 리스트 {change_list_number}에 파일이 {len(change_spec['Files'])}개 있어 삭제할 수 없습니다."
677
+ raise PerforceError(error_message)
729
678
 
730
679
  # 빈 체인지 리스트 삭제
731
680
  self.p4.run_change("-d", change_list_number)
732
681
  return True
733
682
  except P4Exception as e:
734
- self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제 실패")
735
- return False
683
+ error_message = f"체인지 리스트 {change_list_number} 삭제 실패 중 P4Exception 발생: {e}"
684
+ raise PerforceError(error_message)
736
685
 
737
686
  def revert_file(self, file_path: str, change_list_number: int) -> bool:
738
687
  """체인지 리스트에서 특정 파일을 되돌립니다.
@@ -744,16 +693,13 @@ class Perforce:
744
693
  Returns:
745
694
  bool: 되돌리기 성공 시 True, 실패 시 False
746
695
  """
747
- if not self._is_connected():
748
- return False
749
-
750
- self.clear_last_error()
696
+ self._ensure_connected()
751
697
  try:
752
698
  self.p4.run_revert("-c", change_list_number, file_path)
753
699
  return True
754
700
  except P4Exception as e:
755
- self._handle_p4_exception(e, f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기")
756
- return False
701
+ error_message = f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 중 P4Exception 발생: {e}"
702
+ raise PerforceError(error_message)
757
703
 
758
704
  def revert_files(self, change_list_number: int, file_paths: list) -> bool:
759
705
  """체인지 리스트 내의 특정 파일들을 되돌립니다.
@@ -765,16 +711,14 @@ class Perforce:
765
711
  Returns:
766
712
  bool: 모든 파일 되돌리기 성공 시 True, 하나라도 실패 시 False
767
713
  """
768
- if not self._is_connected():
769
- return False
714
+ self._ensure_connected()
770
715
  if not file_paths:
771
716
  return True
772
717
 
773
718
  # 타입 검증: 리스트가 아닌 경우 에러 발생
774
719
  if not isinstance(file_paths, list):
775
720
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 revert_file() 메서드를 사용하세요."
776
- self.last_error = error_msg
777
- raise TypeError(error_msg)
721
+ raise ValidationError(error_msg)
778
722
 
779
723
  all_success = True
780
724
  for file_path in file_paths:
@@ -794,16 +738,14 @@ class Perforce:
794
738
  Returns:
795
739
  bool: 업데이트가 필요한 파일이 있으면 True, 없으면 False
796
740
  """
797
- if not self._is_connected():
798
- return False
741
+ self._ensure_connected()
799
742
  if not file_paths:
800
743
  return False
801
744
 
802
745
  # 타입 검증: 리스트가 아닌 경우 에러 발생
803
746
  if not isinstance(file_paths, list):
804
747
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
805
- self.last_error = error_msg
806
- raise TypeError(error_msg)
748
+ raise ValidationError(error_msg)
807
749
 
808
750
  # 폴더 경로에 재귀적 와일드카드 패턴을 추가
809
751
  processed_paths = []
@@ -814,7 +756,6 @@ class Perforce:
814
756
  else:
815
757
  processed_paths.append(path)
816
758
 
817
- self.clear_last_error()
818
759
  try:
819
760
  sync_preview_results = self.p4.run_sync("-n", processed_paths)
820
761
  needs_update = False
@@ -846,8 +787,8 @@ class Perforce:
846
787
  any("up-to-date" in msg for msg in warning_messages)):
847
788
  return False
848
789
  else:
849
- self._handle_p4_exception(e, f"파일/폴더 업데이트 필요 여부 확인 ({processed_paths})")
850
- return False
790
+ error_message = f"파일/폴더 업데이트 필요 여부 확인 ({processed_paths}) 중 P4Exception 발생: {e}"
791
+ raise PerforceError(error_message)
851
792
 
852
793
  def is_file_in_perforce(self, file_path: str) -> bool:
853
794
  """파일이 Perforce에 속하는지 확인합니다.
@@ -858,10 +799,7 @@ class Perforce:
858
799
  Returns:
859
800
  bool: 파일이 Perforce에 속하면 True, 아니면 False
860
801
  """
861
- if not self._is_connected():
862
- return False
863
-
864
- self.clear_last_error()
802
+ self._ensure_connected()
865
803
  try:
866
804
  # p4 files 명령으로 파일 정보 조회
867
805
  file_info = self.p4.run_files(file_path)
@@ -877,8 +815,8 @@ class Perforce:
877
815
  if any("no such file(s)" in err.lower() for err in self.p4.errors):
878
816
  return False
879
817
  else:
880
- self._handle_p4_exception(e, f"파일 '{file_path}' Perforce 존재 여부 확인")
881
- return False
818
+ error_message = f"파일 '{file_path}' Perforce 존재 여부 확인 중 P4Exception 발생: {e}"
819
+ raise PerforceError(error_message)
882
820
 
883
821
  def sync_files(self, file_paths: list) -> bool:
884
822
  """파일이나 폴더를 동기화합니다.
@@ -890,16 +828,14 @@ class Perforce:
890
828
  Returns:
891
829
  bool: 동기화 성공 시 True, 실패 시 False
892
830
  """
893
- if not self._is_connected():
894
- return False
831
+ self._ensure_connected()
895
832
  if not file_paths:
896
833
  return True
897
834
 
898
835
  # 타입 검증: 리스트가 아닌 경우 에러 발생
899
836
  if not isinstance(file_paths, list):
900
837
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
901
- self.last_error = error_msg
902
- raise TypeError(error_msg)
838
+ raise ValidationError(error_msg)
903
839
 
904
840
  # 폴더 경로에 재귀적 와일드카드 패턴을 추가
905
841
  processed_paths = []
@@ -910,13 +846,12 @@ class Perforce:
910
846
  else:
911
847
  processed_paths.append(path)
912
848
 
913
- self.clear_last_error()
914
849
  try:
915
850
  self.p4.run_sync(processed_paths)
916
851
  return True
917
852
  except P4Exception as e:
918
- self._handle_p4_exception(e, f"파일/폴더 싱크 실패 ({processed_paths})")
919
- return False
853
+ error_message = f"파일/폴더 싱크 실패 ({processed_paths}) 중 P4Exception 발생: {e}"
854
+ raise PerforceError(error_message)
920
855
 
921
856
  def get_default_change_list(self) -> dict:
922
857
  """default change list의 정보를 가져옵니다.
@@ -924,9 +859,7 @@ class Perforce:
924
859
  Returns:
925
860
  dict: get_change_list_by_number와 동일한 형태의 딕셔너리
926
861
  """
927
- if not self._is_connected():
928
- return {}
929
- self.clear_last_error()
862
+ self._ensure_connected()
930
863
  try:
931
864
  opened_files = self.p4.run_opened("-c", "default")
932
865
  files_list = [f.get('clientFile', '') for f in opened_files]
@@ -940,8 +873,8 @@ class Perforce:
940
873
  }
941
874
  return result
942
875
  except P4Exception as e:
943
- self._handle_p4_exception(e, "default change list 정보 조회 실패")
944
- return {}
876
+ error_message = f"default change list 정보 조회 실패 중 P4Exception 발생: {e}"
877
+ raise PerforceError(error_message)
945
878
 
946
879
  def check_files_checked_out_all_users(self, file_paths: list) -> dict:
947
880
  """파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
@@ -961,18 +894,15 @@ class Perforce:
961
894
  }
962
895
  }
963
896
  """
964
- if not self._is_connected():
965
- return {}
897
+ self._ensure_connected()
966
898
  if not file_paths:
967
899
  return {}
968
900
 
969
901
  # 타입 검증: 리스트가 아닌 경우 에러 발생
970
902
  if not isinstance(file_paths, list):
971
903
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 get_file_checkout_info_all_users() 메서드를 사용하세요."
972
- self.last_error = error_msg
973
- raise TypeError(error_msg)
904
+ raise ValidationError(error_msg)
974
905
 
975
- self.clear_last_error()
976
906
  result = {}
977
907
  try:
978
908
  # 각 파일의 상태 확인
@@ -1002,15 +932,16 @@ class Perforce:
1002
932
  # 파일이 perforce에 없거나 접근할 수 없는 경우
1003
933
  if not any("not opened" in err.lower() or "no such file" in err.lower()
1004
934
  for err in self.p4.errors):
1005
- self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
935
+ error_message = f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자) 중 P4Exception 발생: {e}"
936
+ raise PerforceError(error_message)
1006
937
 
1007
938
  result[file_path] = file_status
1008
939
 
1009
940
  return result
1010
941
 
1011
942
  except P4Exception as e:
1012
- self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 - 모든 사용자 ({file_paths})")
1013
- return {}
943
+ error_message = f"파일들 체크아웃 상태 확인 - 모든 사용자 ({file_paths}) 중 P4Exception 발생: {e}"
944
+ raise PerforceError(error_message)
1014
945
 
1015
946
  def is_file_checked_out_by_others(self, file_path: str) -> bool:
1016
947
  """단일 파일이 다른 사용자/워크스페이스에 의해 체크아웃되어 있는지 확인합니다.
@@ -1099,8 +1030,7 @@ class Perforce:
1099
1030
  # 타입 검증: 리스트가 아닌 경우 에러 발생
1100
1031
  if not isinstance(file_paths, list):
1101
1032
  error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out_by_others() 메서드를 사용하세요."
1102
- self.last_error = error_msg
1103
- raise TypeError(error_msg)
1033
+ raise ValidationError(error_msg)
1104
1034
 
1105
1035
  result = self.check_files_checked_out_all_users(file_paths)
1106
1036
  files_by_others = []