Skip to content

Commit bde33d1

Browse files
committed
add SharePoint service with error handling and file management methods
0 parents  commit bde33d1

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import os
2+
import time
3+
from functools import wraps
4+
5+
from office365.runtime.auth.user_credential import UserCredential
6+
from office365.runtime.client_request_exception import ClientRequestException
7+
from office365.sharepoint.client_context import ClientContext
8+
from office365.sharepoint.files.file import File
9+
from office365.sharepoint.folders.folder import Folder
10+
11+
12+
def handle_sharepoint_errors(max_retries=5, delay_seconds=3):
13+
"""
14+
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.
16+
"""
17+
18+
def decorator(func):
19+
@wraps(func)
20+
def wrapper(self, *args, **kwargs):
21+
last_exception = None
22+
for attempt in range(max_retries):
23+
try:
24+
self.ctx.clear()
25+
return func(self, *args, **kwargs)
26+
except ClientRequestException as e:
27+
last_exception = e
28+
if e.response.status_code == 403:
29+
print("Erro 403 (Proibido) detectado. Tentando relogar...")
30+
if not (self.username and self.password):
31+
print("Credenciais não disponíveis para relogin. Abortando.")
32+
raise e
33+
34+
if self.login(self.username, self.password):
35+
print("Relogin bem-sucedido. Tentando a operação novamente.")
36+
try:
37+
# Tenta a operação mais uma vez após o relogin
38+
return func(self, *args, **kwargs)
39+
except ClientRequestException as e2:
40+
print(f"A operação falhou mesmo após o relogin: {e2}")
41+
raise e2
42+
else:
43+
print("Falha ao relogar. Abortando.")
44+
raise e
45+
# --- Erro de servidor (temporário) ---
46+
elif e.response.status_code == 503:
47+
print(
48+
f"Erro 503 (Serviço Indisponível). Tentativa {attempt + 1}/{max_retries} em {delay_seconds}s...")
49+
time.sleep(delay_seconds)
50+
continue # Próxima iteração do loop de retentativa
51+
# --- Outros erros de cliente/servidor ---
52+
else:
53+
print(f"Erro não recuperável encontrado: {e}")
54+
raise e
55+
except Exception as e:
56+
# Captura outras exceções (ex: problemas de rede)
57+
print(f"Uma exceção inesperada ocorreu: {e}. Tentando novamente em {delay_seconds}s...")
58+
last_exception = e
59+
time.sleep(delay_seconds)
60+
61+
# Se todas as tentativas falharem, lança a última exceção capturada
62+
print(f"A operação '{func.__name__}' falhou após {max_retries} tentativas.")
63+
raise last_exception
64+
65+
return wrapper
66+
67+
return decorator
68+
69+
70+
class SharepointService:
71+
72+
def __init__(self, site_url: str):
73+
self.site_url = site_url
74+
self.ctx = ClientContext(self.site_url)
75+
self.username = None
76+
self.password = None
77+
78+
def login(self, username, password):
79+
"""Autentica no site do SharePoint usando as credenciais fornecidas."""
80+
print(f"Fazendo login no SharePoint com o usuário {username}...")
81+
self.username = username
82+
self.password = password
83+
try:
84+
self.ctx.with_credentials(UserCredential(self.username, self.password))
85+
self.ctx.load(self.ctx.web)
86+
self.ctx.execute_query()
87+
print("Login realizado com sucesso.")
88+
return True
89+
except ClientRequestException as e:
90+
print(f"Erro ao fazer login: {e}")
91+
return False
92+
93+
@handle_sharepoint_errors()
94+
def obter_pasta(self, caminho_pasta: str) -> Folder | None:
95+
"""Obtém um objeto Folder a partir do seu caminho relativo no servidor."""
96+
try:
97+
folder = self.ctx.web.get_folder_by_server_relative_url(caminho_pasta)
98+
folder.get().execute_query()
99+
return folder
100+
except ClientRequestException as e:
101+
if e.response.status_code == 404:
102+
return None
103+
raise e
104+
105+
@handle_sharepoint_errors()
106+
def obter_arquivo(self, caminho_arquivo: str) -> File | None:
107+
"""Obtém um objeto File a partir do seu caminho relativo no servidor."""
108+
try:
109+
folder = self.ctx.web.get_file_by_server_relative_url(caminho_arquivo)
110+
folder.get().execute_query()
111+
return folder
112+
except ClientRequestException as e:
113+
if e.response.status_code == 404:
114+
return None
115+
raise e
116+
117+
@handle_sharepoint_errors()
118+
def listar_arquivos(self, pasta_alvo: Folder | str):
119+
"""Lista todos os arquivos dentro de uma pasta específica."""
120+
if isinstance(pasta_alvo, str):
121+
pasta = self.obter_pasta(pasta_alvo)
122+
if pasta is None:
123+
raise FileNotFoundError(f"A pasta '{pasta_alvo}' não foi encontrada.")
124+
pasta_alvo = pasta
125+
126+
files = pasta_alvo.files.expand(["ModifiedBy"]).get().execute_query()
127+
return files
128+
129+
@handle_sharepoint_errors()
130+
def listar_pastas(self, pasta_pai: Folder | str):
131+
"""Lista todas as subpastas dentro de uma pasta pai."""
132+
if isinstance(pasta_pai, str):
133+
pasta = self.obter_pasta(pasta_pai)
134+
if pasta is None:
135+
raise FileNotFoundError(f"A pasta '{pasta_pai}' não foi encontrada.")
136+
pasta_pai = pasta
137+
138+
folders = pasta_pai.folders
139+
folders.expand(["ModifiedBy"]).get().execute_query()
140+
return folders
141+
142+
@handle_sharepoint_errors()
143+
def criar_pasta(self, pasta_pai: Folder | str, nome_nova_pasta: str):
144+
"""Cria uma nova pasta se ela ainda não existir."""
145+
if isinstance(pasta_pai, str):
146+
pasta = self.obter_pasta(pasta_pai)
147+
if pasta is None:
148+
raise FileNotFoundError(f"A pasta pai '{pasta_pai}' não foi encontrada.")
149+
pasta_pai = pasta
150+
151+
# Verifica se a pasta já existe para evitar erro
152+
pastas_existentes = self.listar_pastas(pasta_pai)
153+
for p in pastas_existentes:
154+
if p.name == nome_nova_pasta:
155+
print(f"Pasta '{nome_nova_pasta}' já existe.")
156+
return p
157+
158+
print(f"Criando pasta '{nome_nova_pasta}'...")
159+
nova_pasta = pasta_pai.folders.add(nome_nova_pasta).execute_query()
160+
return nova_pasta
161+
162+
@handle_sharepoint_errors()
163+
def baixar_arquivo(self, arquivo_sp: File | str, caminho_download: str):
164+
"""Baixa um arquivo do SharePoint para um caminho local."""
165+
if isinstance(arquivo_sp, str):
166+
file_to_download = self.ctx.web.get_file_by_server_relative_url(arquivo_sp)
167+
else:
168+
file_to_download = arquivo_sp
169+
170+
with open(caminho_download, "wb") as local_file:
171+
file_to_download.download(local_file)
172+
print(f"Arquivo '{os.path.basename(str(arquivo_sp))}' baixado para '{caminho_download}'.")
173+
174+
@handle_sharepoint_errors()
175+
def enviar_arquivo(self, pasta_destino: Folder | str, arquivo_local: str, nome_arquivo_sp: str = None):
176+
"""Envia um arquivo local para uma pasta no SharePoint."""
177+
if isinstance(pasta_destino, str):
178+
pasta = self.obter_pasta(pasta_destino)
179+
if pasta is None:
180+
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
181+
pasta_destino = pasta
182+
183+
if not nome_arquivo_sp:
184+
nome_arquivo_sp = os.path.basename(arquivo_local)
185+
186+
with open(arquivo_local, 'rb') as file_content:
187+
fbytes = file_content.read()
188+
189+
print(f"Enviando arquivo '{nome_arquivo_sp}'...")
190+
arquivo = pasta_destino.upload_file(nome_arquivo_sp, fbytes).execute_query()
191+
print(f"Arquivo '{nome_arquivo_sp}' enviado com sucesso!")
192+
return arquivo
193+
194+
@handle_sharepoint_errors()
195+
def mover_arquivo(self, arquivo_origem: File, pasta_destino: Folder | str):
196+
"""Move um arquivo para outra pasta de forma atômica."""
197+
if isinstance(pasta_destino, str):
198+
pasta = self.obter_pasta(pasta_destino)
199+
if pasta is None:
200+
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
201+
pasta_destino = pasta
202+
203+
# Constrói a URL de destino
204+
url_destino = os.path.join(pasta_destino.properties['ServerRelativeUrl'], arquivo_origem.name)
205+
206+
print(f"Movendo '{arquivo_origem.name}' para '{pasta_destino.properties['ServerRelativeUrl']}'...")
207+
arquivo_origem.copyto(url_destino, True).execute_query()
208+
arquivo_origem.delete_object().execute_query()
209+
print("Arquivo movido com sucesso.")
210+
return self.ctx.web.get_file_by_server_relative_url(url_destino)
211+
212+
@handle_sharepoint_errors()
213+
def copiar_arquivo(self, arquivo_origem: File, pasta_destino: Folder | str):
214+
"""Copia um arquivo para outra pasta."""
215+
if isinstance(pasta_destino, str):
216+
pasta = self.obter_pasta(pasta_destino)
217+
if pasta is None:
218+
raise FileNotFoundError(f"A pasta de destino '{pasta_destino}' não foi encontrada.")
219+
pasta_destino = pasta
220+
221+
print(f"Copiando '{arquivo_origem.name}' para '{pasta_destino.name}'...")
222+
novo_arquivo = arquivo_origem.copyto(pasta_destino, True).execute_query()
223+
print("Arquivo copiado com sucesso.")
224+
return novo_arquivo
225+
226+
def obter_pasta_por_nome(self, pasta_raiz: Folder, nome):
227+
pastas = list(self.listar_pastas(pasta_raiz))
228+
pasta_encontrada = next((pasta for pasta in pastas if nome in pasta.name), None)
229+
return pasta_encontrada

0 commit comments

Comments
 (0)