2
2
import time
3
3
from functools import wraps
4
4
5
- import requests
6
-
7
5
from office365 .runtime .auth .user_credential import UserCredential
8
6
from office365 .runtime .client_request_exception import ClientRequestException
9
7
from office365 .sharepoint .client_context import ClientContext
10
8
from office365 .sharepoint .files .file import File
11
9
from office365 .sharepoint .folders .folder import Folder
12
10
13
11
14
- def handle_sharepoint_errors (max_retries : int = 5 , delay_seconds : int = 3 ):
12
+ def handle_sharepoint_errors (max_retries : int = 5 , delay_seconds : int = 3 , timeout_seconds : int = 120 ):
15
13
"""
16
14
Decorador para tratar exceções de requisições do SharePoint, com lógica
17
- de nova autenticação e novas tentativas para erros comuns .
15
+ de nova autenticação, novas tentativas e controle de timeout .
18
16
19
17
Args:
20
18
max_retries (int): Número máximo de tentativas para erros recuperáveis.
21
19
delay_seconds (int): Atraso entre as tentativas.
20
+ timeout_seconds (int): Tempo máximo de espera para a execução da operação em segundos.
22
21
"""
23
22
24
23
def decorator (func ):
25
24
@wraps (func )
26
25
def wrapper (self , * args , ** kwargs ):
27
26
last_exception = None
28
-
29
27
for attempt in range (max_retries ):
30
28
try :
31
- # A chamada da função original acontece aqui.
29
+ self . ctx . clear ()
32
30
return func (self , * args , ** kwargs )
33
-
34
- except requests .exceptions .Timeout as e :
35
- # Captura a exceção de timeout da biblioteca 'requests'.
36
- print (
37
- f"Erro: A operação '{ func .__name__ } ' excedeu o tempo limite de { self .timeout } segundos. "
38
- f"Tentativa { attempt + 1 } /{ max_retries } ..." )
39
- last_exception = e
40
- time .sleep (delay_seconds )
41
- continue
42
-
43
31
except ClientRequestException as e :
44
32
last_exception = e
45
- if e .response .status_code == 429 : # Too Many Requests
46
- # O SharePoint pode retornar um header 'Retry-After'
47
- retry_after = int (e .response .headers .get ("Retry-After" , 60 * (attempt + 1 )))
48
- print (f"Erro 429 (Muitas solicitações) detectado. Aguardando { retry_after } segundos..." )
49
- time .sleep (retry_after )
33
+ if e .response .status_code == 429 :
34
+ wait_time = 60 * (attempt + 1 )
35
+ print (f"Erro 429 (Muitas solicitações) detectado. Aguardando { wait_time } segundos..." )
36
+ time .sleep (wait_time )
50
37
continue
51
- elif e .response .status_code == 403 : # Forbidden
38
+ elif e .response .status_code == 403 :
52
39
print ("Erro 403 (Proibido) detectado. Tentando relogar..." )
53
40
if not (self .username and self .password ):
54
41
print ("Credenciais não disponíveis para relogin. Abortando." )
55
42
raise e
56
43
57
44
if self .login (self .username , self .password ):
58
45
print ("Relogin bem-sucedido. Tentando a operação novamente." )
46
+ # Tenta novamente a operação dentro do mesmo loop
59
47
continue
60
48
else :
61
49
print ("Falha ao relogar. Abortando." )
62
50
raise e
63
- elif e .response .status_code in [ 503 , 504 ]: # Service Unavailable / Gateway Timeout
51
+ elif e .response .status_code == 503 :
64
52
print (
65
- f"Erro { e .response .status_code } (Servidor indisponível/sobrecarregado). "
66
- f"Tentativa { attempt + 1 } /{ max_retries } em { delay_seconds } s..." )
53
+ f"Erro 503 (Serviço Indisponível). Tentativa { attempt + 1 } /{ max_retries } em { delay_seconds } s..." )
67
54
time .sleep (delay_seconds )
68
55
continue
69
56
else :
70
- print (f"Erro de cliente não recuperável encontrado: { e } " )
57
+ print (f"Erro não recuperável encontrado: { e } " )
71
58
raise e
72
59
73
60
except Exception as e :
@@ -98,7 +85,6 @@ def __init__(self, site_url: str, timeout_seconds: int = 90):
98
85
self .password = None
99
86
self .timeout = timeout_seconds
100
87
101
- # **SOLUÇÃO**: Adiciona um manipulador de eventos para definir o timeout ANTES de cada requisição.
102
88
self .ctx .pending_request ().beforeExecute += self ._set_request_timeout
103
89
104
90
def _set_request_timeout (self , request_options ):
@@ -107,7 +93,7 @@ def _set_request_timeout(self, request_options):
107
93
"""
108
94
request_options .timeout = self .timeout
109
95
110
- def login (self , username : str , password : str ) -> bool :
96
+ def login (self , username , password ) :
111
97
"""Autentica no site do SharePoint usando as credenciais fornecidas."""
112
98
print (f"Fazendo login no SharePoint com o usuário { username } ..." )
113
99
self .username = username
@@ -118,7 +104,7 @@ def login(self, username: str, password: str) -> bool:
118
104
self .ctx .execute_query ()
119
105
print ("Login realizado com sucesso." )
120
106
return True
121
- except ( ClientRequestException , requests . exceptions . Timeout ) as e :
107
+ except ClientRequestException as e :
122
108
print (f"Erro ao fazer login: { e } " )
123
109
return False
124
110
@@ -173,7 +159,19 @@ def listar_pastas(self, pasta_pai: Folder | str):
173
159
174
160
@handle_sharepoint_errors ()
175
161
def criar_pasta (self , pasta_pai : Folder | str , nome_pasta : str ):
176
- """Cria uma nova pasta no Sharepoint, suportando criação de subpastas com "/" """
162
+ """
163
+ Cria uma nova pasta no Sharepoint, suportando criação de subpastas com "/"
164
+
165
+ Args:
166
+ pasta_pai: Caminho ou objeto Folder onde a nova pasta será criada
167
+ nome_pasta: Nome da nova pasta a ser criada, pode incluir "/" para criar subpastas
168
+
169
+ Returns:
170
+ O objeto da pasta criada
171
+
172
+ Raises:
173
+ Exception: Se a pasta pai não for encontrada
174
+ """
177
175
if isinstance (pasta_pai , str ):
178
176
pasta = self .obter_pasta (pasta_pai )
179
177
if pasta is None :
@@ -183,35 +181,33 @@ def criar_pasta(self, pasta_pai: Folder | str, nome_pasta: str):
183
181
if "/" in nome_pasta :
184
182
path_parts = nome_pasta .split ("/" )
185
183
current_folder = pasta_pai
184
+
186
185
for part in path_parts :
187
186
if part :
188
187
current_folder = self .criar_pasta (current_folder , part )
188
+
189
189
return current_folder
190
190
else :
191
- subpastas = list ( self .listar_pastas (pasta_pai ) )
192
- pasta = next ((subpasta for subpasta in subpastas if nome_pasta == subpasta .name ), None )
191
+ subpastas = self .listar_pastas (pasta_pai )
192
+ pasta = next ((subpasta for subpasta in subpastas if nome_pasta in str ( subpasta .name ) ), None )
193
193
if pasta is not None :
194
194
return pasta
195
- print (f"Criando pasta { nome_pasta } ..." )
196
- pasta = pasta_pai .folders .add (nome_pasta ).execute_query ()
195
+ print ("Criando pasta {0}..." .format (nome_pasta ))
196
+ pasta = pasta_pai .folders .add (nome_pasta )
197
+ pasta .execute_query ()
197
198
return pasta
198
199
199
200
@handle_sharepoint_errors ()
200
201
def baixar_arquivo (self , arquivo_sp : File | str , caminho_download : str ):
201
202
"""Baixa um arquivo do SharePoint para um caminho local."""
202
- nome_arquivo = ""
203
203
if isinstance (arquivo_sp , str ):
204
- file_to_download = self .obter_arquivo (arquivo_sp )
205
- if file_to_download is None :
206
- raise FileNotFoundError (f"Arquivo '{ arquivo_sp } ' não encontrado no SharePoint." )
207
- nome_arquivo = file_to_download .name
204
+ file_to_download = self .ctx .web .get_file_by_server_relative_url (arquivo_sp )
208
205
else :
209
206
file_to_download = arquivo_sp
210
- nome_arquivo = arquivo_sp .name
211
207
212
208
with open (caminho_download , "wb" ) as local_file :
213
- file_to_download .download (local_file ).execute_query ()
214
- print (f"Arquivo '{ nome_arquivo } ' baixado para '{ caminho_download } '." )
209
+ file_to_download .download_session (local_file ).execute_query ()
210
+ print (f"Arquivo '{ os . path . basename ( str ( arquivo_sp )) } ' baixado para '{ caminho_download } '." )
215
211
216
212
@handle_sharepoint_errors ()
217
213
def enviar_arquivo (self , pasta_destino : Folder | str , arquivo_local : str , nome_arquivo_sp : str = None ):
@@ -222,12 +218,14 @@ def enviar_arquivo(self, pasta_destino: Folder | str, arquivo_local: str, nome_a
222
218
raise FileNotFoundError (f"A pasta de destino '{ pasta_destino } ' não foi encontrada." )
223
219
pasta_destino = pasta
224
220
225
- nome_arquivo_sp = nome_arquivo_sp or os .path .basename (arquivo_local )
221
+ if not nome_arquivo_sp :
222
+ nome_arquivo_sp = os .path .basename (arquivo_local )
226
223
227
224
with open (arquivo_local , 'rb' ) as file_content :
228
- print (f"Enviando arquivo '{ nome_arquivo_sp } '..." )
229
- arquivo = pasta_destino .files .upload (nome_arquivo_sp , file_content ).execute_query ()
225
+ fbytes = file_content .read ()
230
226
227
+ print (f"Enviando arquivo '{ nome_arquivo_sp } '..." )
228
+ arquivo = pasta_destino .upload_file (nome_arquivo_sp , fbytes ).execute_query ()
231
229
print (f"Arquivo '{ nome_arquivo_sp } ' enviado com sucesso!" )
232
230
return arquivo
233
231
@@ -240,46 +238,48 @@ def mover_arquivo(self, arquivo_origem: File, pasta_destino: Folder | str):
240
238
raise FileNotFoundError (f"A pasta de destino '{ pasta_destino } ' não foi encontrada." )
241
239
pasta_destino = pasta
242
240
243
- print (f"Movendo '{ arquivo_origem .name } ' para '{ pasta_destino .serverRelativeUrl } '..." )
244
- novo_caminho = f"{ pasta_destino .serverRelativeUrl } /{ arquivo_origem .name } "
245
- arquivo_origem .moveto (novo_caminho , 1 ).execute_query ()
246
- print ("Arquivo movido com sucesso." )
241
+ print (f"Movendo '{ arquivo_origem .name } ' para '{ pasta_destino .name } '..." )
242
+
243
+ novo_arquivo = arquivo_origem .moveto (pasta_destino , flag = 1 )
244
+ novo_arquivo .execute_query ()
245
+
246
+ return novo_arquivo
247
247
248
248
@handle_sharepoint_errors ()
249
- def copiar_arquivo (self , arquivo_origem : File , pasta_destino : Folder | str , novo_nome : str = None ):
249
+ def copiar_arquivo (self , arquivo_origem : File , pasta_destino : Folder | str ):
250
250
"""Copia um arquivo para outra pasta."""
251
251
if isinstance (pasta_destino , str ):
252
252
pasta = self .obter_pasta (pasta_destino )
253
253
if pasta is None :
254
254
raise FileNotFoundError (f"A pasta de destino '{ pasta_destino } ' não foi encontrada." )
255
255
pasta_destino = pasta
256
256
257
- nome_final = novo_nome or arquivo_origem .name
258
- print (f"Copiando '{ arquivo_origem .name } ' para '{ pasta_destino .serverRelativeUrl } /{ nome_final } '..." )
259
- arquivo_origem .copyto (f"{ pasta_destino .serverRelativeUrl } /{ nome_final } " , True ).execute_query ()
257
+ print (f"Copiando '{ arquivo_origem .name } ' para '{ pasta_destino .name } '..." )
258
+ novo_arquivo = arquivo_origem .copyto (pasta_destino , True ).execute_query ()
260
259
print ("Arquivo copiado com sucesso." )
260
+ return novo_arquivo
261
261
262
262
@handle_sharepoint_errors ()
263
- def renomear_arquivo (self , arquivo : File , novo_nome : str ):
263
+ def renomear_arquivo (self , arquivo : File , novo_nome : str ) -> File :
264
264
"""Renomeia um arquivo no SharePoint."""
265
265
print (f"Renomeando '{ arquivo .name } ' para '{ novo_nome } '..." )
266
- arquivo .rename (novo_nome ).execute_query ()
266
+ novo_arquivo = arquivo .rename (novo_nome )
267
+ novo_arquivo .execute_query ()
267
268
print ("Arquivo renomeado com sucesso." )
269
+ return novo_arquivo
268
270
269
271
@handle_sharepoint_errors ()
270
- def compartilhar_item (self , item : File | Folder , tipo : int = 0 ):
271
- """Cria um link de compartilhamento para um item. tipo 0: View, 1: Edit"""
272
- resultado = item . share_link ( tipo ) .execute_query ()
272
+ def compartilhar_item (self , item : File | Folder , tipo : int ):
273
+ resultado = item .share_link ( tipo )
274
+ resultado .execute_query ()
273
275
return resultado .value .sharingLinkInfo .Url
274
276
275
- def obter_pasta_por_nome (self , pasta_raiz : Folder , nome : str ) -> Folder | None :
276
- """Busca uma subpasta pelo nome exato dentro de uma pasta raiz."""
277
- pastas = self .listar_pastas (pasta_raiz )
277
+ def obter_pasta_por_nome (self , pasta_raiz : Folder , nome ):
278
+ pastas = list (self .listar_pastas (pasta_raiz ))
278
279
pasta_encontrada = next ((pasta for pasta in pastas if nome in pasta .name ), None )
279
280
return pasta_encontrada
280
281
281
- def obter_arquivo_por_nome (self , pasta : Folder , nome : str ) -> File | None :
282
- """Busca um arquivo pelo nome exato dentro de uma pasta."""
283
- arquivos = self .listar_arquivos (pasta )
282
+ def obter_arquivo_por_nome (self , pasta : Folder , nome ):
283
+ arquivos = list (self .listar_arquivos (pasta ))
284
284
arquivo_encontrado = next ((arquivo for arquivo in arquivos if nome in arquivo .name ), None )
285
285
return arquivo_encontrado
0 commit comments