1
1
import os
2
2
import time
3
3
from functools import wraps
4
+ import timeout_decorator
5
+ from timeout_decorator import TimeoutError
4
6
5
7
from office365 .runtime .auth .user_credential import UserCredential
6
8
from office365 .runtime .client_request_exception import ClientRequestException
9
11
from office365 .sharepoint .folders .folder import Folder
10
12
11
13
12
- def handle_sharepoint_errors (max_retries = 5 , delay_seconds = 3 ):
14
+ def handle_sharepoint_errors (max_retries : int = 5 , delay_seconds : int = 3 , timeout_seconds : int = 120 ):
13
15
"""
14
16
Decorador para tratar exceções de requisições do SharePoint, com lógica
15
- de nova autenticação para erros 403 e novas tentativas para erros 503.
17
+ de nova autenticação, novas tentativas e controle de timeout.
18
+
19
+ Args:
20
+ max_retries (int): Número máximo de tentativas para erros recuperáveis.
21
+ delay_seconds (int): Atraso entre as tentativas.
22
+ timeout_seconds (int): Tempo máximo de espera para a execução da operação em segundos.
16
23
"""
17
24
18
25
def decorator (func ):
19
26
@wraps (func )
20
27
def wrapper (self , * args , ** kwargs ):
21
28
last_exception = None
29
+
30
+ # Envolve a lógica de retentativas em uma função para aplicar o timeout a cada tentativa.
31
+ @timeout_decorator .timeout (timeout_seconds , use_signals = False )
32
+ def run_operation ():
33
+ return func (self , * args , ** kwargs )
34
+
22
35
for attempt in range (max_retries ):
23
36
try :
24
37
self .ctx .clear ()
25
- return func (self , * args , ** kwargs )
38
+ # A chamada com timeout é feita aqui
39
+ return run_operation ()
40
+
41
+ except TimeoutError :
42
+ # Se a operação exceder o tempo limite, capturamos o erro aqui.
43
+ print (
44
+ f"Erro: A operação '{ func .__name__ } ' excedeu o tempo limite de { timeout_seconds } segundos. Tentativa { attempt + 1 } /{ max_retries } ." )
45
+ last_exception = TimeoutError (
46
+ f"A operação '{ func .__name__ } ' excedeu o tempo limite de { timeout_seconds } s." )
47
+ # Continua para a próxima tentativa após um delay
48
+ time .sleep (delay_seconds )
49
+ continue
50
+
26
51
except ClientRequestException as e :
27
52
last_exception = e
28
53
if e .response .status_code == 429 :
29
- print (f"Erro 429 (Muitas solicitações) detectado. Aguardando 60 segundos..." )
30
- time .sleep (60 * (attempt + 1 ))
54
+ wait_time = 60 * (attempt + 1 )
55
+ print (f"Erro 429 (Muitas solicitações) detectado. Aguardando { wait_time } segundos..." )
56
+ time .sleep (wait_time )
31
57
continue
32
58
elif e .response .status_code == 403 :
33
59
print ("Erro 403 (Proibido) detectado. Tentando relogar..." )
@@ -37,31 +63,25 @@ def wrapper(self, *args, **kwargs):
37
63
38
64
if self .login (self .username , self .password ):
39
65
print ("Relogin bem-sucedido. Tentando a operação novamente." )
40
- try :
41
- return func (self , * args , ** kwargs )
42
- except ClientRequestException as e2 :
43
- print (f"A operação falhou mesmo após o relogin: { e2 } " )
44
- raise e2
66
+ # Tenta novamente a operação dentro do mesmo loop
67
+ continue
45
68
else :
46
69
print ("Falha ao relogar. Abortando." )
47
70
raise e
48
- # --- Erro de servidor (temporário) ---
49
71
elif e .response .status_code == 503 :
50
72
print (
51
73
f"Erro 503 (Serviço Indisponível). Tentativa { attempt + 1 } /{ max_retries } em { delay_seconds } s..." )
52
74
time .sleep (delay_seconds )
53
- continue # Próxima iteração do loop de retentativa
54
- # --- Outros erros de cliente/servidor ---
75
+ continue
55
76
else :
56
77
print (f"Erro não recuperável encontrado: { e } " )
57
78
raise e
79
+
58
80
except Exception as e :
59
- # Captura outras exceções (ex: problemas de rede)
60
81
print (f"Uma exceção inesperada ocorreu: { e } . Tentando novamente em { delay_seconds } s..." )
61
82
last_exception = e
62
83
time .sleep (delay_seconds )
63
84
64
- # Se todas as tentativas falharem, lança a última exceção capturada
65
85
print (f"A operação '{ func .__name__ } ' falhou após { max_retries } tentativas." )
66
86
raise last_exception
67
87
@@ -71,7 +91,6 @@ def wrapper(self, *args, **kwargs):
71
91
72
92
73
93
class SharepointService :
74
-
75
94
def __init__ (self , site_url : str ):
76
95
self .site_url = site_url
77
96
self .ctx = ClientContext (self .site_url )
@@ -105,18 +124,6 @@ def obter_pasta(self, caminho_pasta: str) -> Folder | None:
105
124
return None
106
125
raise e
107
126
108
- @handle_sharepoint_errors ()
109
- def obter_arquivo (self , caminho_arquivo : str ) -> File | None :
110
- """Obtém um objeto File a partir do seu caminho relativo no servidor."""
111
- try :
112
- file = self .ctx .web .get_file_by_server_relative_url (caminho_arquivo )
113
- file .get ().execute_query ()
114
- return file
115
- except ClientRequestException as e :
116
- if e .response .status_code == 404 :
117
- return None
118
- raise e
119
-
120
127
@handle_sharepoint_errors ()
121
128
def listar_arquivos (self , pasta_alvo : Folder | str ):
122
129
"""Lista todos os arquivos dentro de uma pasta específica."""
@@ -129,6 +136,18 @@ def listar_arquivos(self, pasta_alvo: Folder | str):
129
136
files .expand (["ModifiedBy" ]).get ().execute_query ()
130
137
return files
131
138
139
+ @handle_sharepoint_errors ()
140
+ def obter_arquivo (self , caminho_arquivo : str ) -> File | None :
141
+ """Obtém um objeto File a partir do seu caminho relativo no servidor."""
142
+ try :
143
+ file = self .ctx .web .get_file_by_server_relative_url (caminho_arquivo )
144
+ file .get ().execute_query ()
145
+ return file
146
+ except ClientRequestException as e :
147
+ if e .response .status_code == 404 :
148
+ return None
149
+ raise e
150
+
132
151
@handle_sharepoint_errors ()
133
152
def listar_pastas (self , pasta_pai : Folder | str ):
134
153
"""Lista todas as subpastas dentro de uma pasta pai."""
@@ -267,4 +286,4 @@ def obter_pasta_por_nome(self, pasta_raiz: Folder, nome):
267
286
def obter_arquivo_por_nome (self , pasta : Folder , nome ):
268
287
arquivos = list (self .listar_arquivos (pasta ))
269
288
arquivo_encontrado = next ((arquivo for arquivo in arquivos if nome in arquivo .name ), None )
270
- return arquivo_encontrado
289
+ return arquivo_encontrado
0 commit comments