Skip to content

Commit f40569a

Browse files
committed
Update sharepoint_service.py
1 parent dbe7cfd commit f40569a

File tree

2 files changed

+65
-65
lines changed

2 files changed

+65
-65
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "office365-service"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
description = "Add your description here"
55
readme = "README.md"
66
authors = [

src/office365_service/sharepoint_service.py

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,59 @@
22
import time
33
from functools import wraps
44

5-
import requests
6-
75
from office365.runtime.auth.user_credential import UserCredential
86
from office365.runtime.client_request_exception import ClientRequestException
97
from office365.sharepoint.client_context import ClientContext
108
from office365.sharepoint.files.file import File
119
from office365.sharepoint.folders.folder import Folder
1210

1311

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):
1513
"""
1614
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.
1816
1917
Args:
2018
max_retries (int): Número máximo de tentativas para erros recuperáveis.
2119
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.
2221
"""
2322

2423
def decorator(func):
2524
@wraps(func)
2625
def wrapper(self, *args, **kwargs):
2726
last_exception = None
28-
2927
for attempt in range(max_retries):
3028
try:
31-
# A chamada da função original acontece aqui.
29+
self.ctx.clear()
3230
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-
4331
except ClientRequestException as e:
4432
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)
5037
continue
51-
elif e.response.status_code == 403: # Forbidden
38+
elif e.response.status_code == 403:
5239
print("Erro 403 (Proibido) detectado. Tentando relogar...")
5340
if not (self.username and self.password):
5441
print("Credenciais não disponíveis para relogin. Abortando.")
5542
raise e
5643

5744
if self.login(self.username, self.password):
5845
print("Relogin bem-sucedido. Tentando a operação novamente.")
46+
# Tenta novamente a operação dentro do mesmo loop
5947
continue
6048
else:
6149
print("Falha ao relogar. Abortando.")
6250
raise e
63-
elif e.response.status_code in [503, 504]: # Service Unavailable / Gateway Timeout
51+
elif e.response.status_code == 503:
6452
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...")
6754
time.sleep(delay_seconds)
6855
continue
6956
else:
70-
print(f"Erro de cliente não recuperável encontrado: {e}")
57+
print(f"Erro não recuperável encontrado: {e}")
7158
raise e
7259

7360
except Exception as e:
@@ -98,7 +85,6 @@ def __init__(self, site_url: str, timeout_seconds: int = 90):
9885
self.password = None
9986
self.timeout = timeout_seconds
10087

101-
# **SOLUÇÃO**: Adiciona um manipulador de eventos para definir o timeout ANTES de cada requisição.
10288
self.ctx.pending_request().beforeExecute += self._set_request_timeout
10389

10490
def _set_request_timeout(self, request_options):
@@ -107,7 +93,7 @@ def _set_request_timeout(self, request_options):
10793
"""
10894
request_options.timeout = self.timeout
10995

110-
def login(self, username: str, password: str) -> bool:
96+
def login(self, username, password):
11197
"""Autentica no site do SharePoint usando as credenciais fornecidas."""
11298
print(f"Fazendo login no SharePoint com o usuário {username}...")
11399
self.username = username
@@ -118,7 +104,7 @@ def login(self, username: str, password: str) -> bool:
118104
self.ctx.execute_query()
119105
print("Login realizado com sucesso.")
120106
return True
121-
except (ClientRequestException, requests.exceptions.Timeout) as e:
107+
except ClientRequestException as e:
122108
print(f"Erro ao fazer login: {e}")
123109
return False
124110

@@ -173,7 +159,19 @@ def listar_pastas(self, pasta_pai: Folder | str):
173159

174160
@handle_sharepoint_errors()
175161
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+
"""
177175
if isinstance(pasta_pai, str):
178176
pasta = self.obter_pasta(pasta_pai)
179177
if pasta is None:
@@ -183,35 +181,33 @@ def criar_pasta(self, pasta_pai: Folder | str, nome_pasta: str):
183181
if "/" in nome_pasta:
184182
path_parts = nome_pasta.split("/")
185183
current_folder = pasta_pai
184+
186185
for part in path_parts:
187186
if part:
188187
current_folder = self.criar_pasta(current_folder, part)
188+
189189
return current_folder
190190
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)
193193
if pasta is not None:
194194
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()
197198
return pasta
198199

199200
@handle_sharepoint_errors()
200201
def baixar_arquivo(self, arquivo_sp: File | str, caminho_download: str):
201202
"""Baixa um arquivo do SharePoint para um caminho local."""
202-
nome_arquivo = ""
203203
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)
208205
else:
209206
file_to_download = arquivo_sp
210-
nome_arquivo = arquivo_sp.name
211207

212208
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}'.")
215211

216212
@handle_sharepoint_errors()
217213
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
222218
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
223219
pasta_destino = pasta
224220

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)
226223

227224
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()
230226

227+
print(f"Enviando arquivo '{nome_arquivo_sp}'...")
228+
arquivo = pasta_destino.upload_file(nome_arquivo_sp, fbytes).execute_query()
231229
print(f"Arquivo '{nome_arquivo_sp}' enviado com sucesso!")
232230
return arquivo
233231

@@ -240,46 +238,48 @@ def mover_arquivo(self, arquivo_origem: File, pasta_destino: Folder | str):
240238
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
241239
pasta_destino = pasta
242240

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
247247

248248
@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):
250250
"""Copia um arquivo para outra pasta."""
251251
if isinstance(pasta_destino, str):
252252
pasta = self.obter_pasta(pasta_destino)
253253
if pasta is None:
254254
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
255255
pasta_destino = pasta
256256

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()
260259
print("Arquivo copiado com sucesso.")
260+
return novo_arquivo
261261

262262
@handle_sharepoint_errors()
263-
def renomear_arquivo(self, arquivo: File, novo_nome: str):
263+
def renomear_arquivo(self, arquivo: File, novo_nome: str) -> File:
264264
"""Renomeia um arquivo no SharePoint."""
265265
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()
267268
print("Arquivo renomeado com sucesso.")
269+
return novo_arquivo
268270

269271
@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()
273275
return resultado.value.sharingLinkInfo.Url
274276

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))
278279
pasta_encontrada = next((pasta for pasta in pastas if nome in pasta.name), None)
279280
return pasta_encontrada
280281

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))
284284
arquivo_encontrado = next((arquivo for arquivo in arquivos if nome in arquivo.name), None)
285285
return arquivo_encontrado

0 commit comments

Comments
 (0)