csc-cia-stne 0.1.16__py3-none-any.whl → 0.1.17__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.
- csc_cia_stne/google_drive.py +674 -129
- {csc_cia_stne-0.1.16.dist-info → csc_cia_stne-0.1.17.dist-info}/METADATA +1 -1
- {csc_cia_stne-0.1.16.dist-info → csc_cia_stne-0.1.17.dist-info}/RECORD +6 -6
- {csc_cia_stne-0.1.16.dist-info → csc_cia_stne-0.1.17.dist-info}/WHEEL +0 -0
- {csc_cia_stne-0.1.16.dist-info → csc_cia_stne-0.1.17.dist-info}/licenses/LICENCE +0 -0
- {csc_cia_stne-0.1.16.dist-info → csc_cia_stne-0.1.17.dist-info}/top_level.txt +0 -0
csc_cia_stne/google_drive.py
CHANGED
@@ -1,59 +1,145 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
from googleapiclient.
|
4
|
-
from googleapiclient.
|
5
|
-
from
|
6
|
-
from
|
1
|
+
import os # google_drive.py da lib - Operações do sistema operacional
|
2
|
+
import base64 # Codificação/decodificação base64 para manipulação de dados
|
3
|
+
from googleapiclient.discovery import build # Construção do cliente da API do Google
|
4
|
+
from googleapiclient.http import MediaFileUpload # Upload de arquivos para APIs do Google
|
5
|
+
from googleapiclient.http import MediaIoBaseUpload # Upload de streams/buffers para APIs do Google
|
6
|
+
from googleapiclient.errors import HttpError # Tratamento de erros HTTP das APIs do Google
|
7
|
+
from io import BytesIO # Manipulação de streams de bytes em memória
|
8
|
+
from google.oauth2 import service_account # Autenticação com conta de serviço do Google
|
7
9
|
from .utilitarios.validations.GoogleDriveValidator import (
|
8
10
|
InitParamsValidator,
|
9
11
|
CreateFolderValidator,
|
10
12
|
ListFolderValidator,
|
11
13
|
UploadValidator,
|
12
14
|
)
|
13
|
-
from pydantic import ValidationError
|
15
|
+
from pydantic import ValidationError # Exceções de validação do Pydantic
|
16
|
+
from typing import Iterable, Optional, Dict, List, Union, Any # Type hints para melhor documentação
|
14
17
|
|
15
18
|
|
16
19
|
class GoogleDrive:
|
17
20
|
"""
|
18
|
-
Classe responsável por gerenciar operações no Google Drive
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
Classe responsável por gerenciar operações no Google Drive usando padrão Singleton.
|
22
|
+
|
23
|
+
Esta classe implementa o padrão Singleton, garantindo que apenas uma instância
|
24
|
+
seja criada durante a execução da aplicação. Fornece funcionalidades para:
|
25
|
+
- Upload de arquivos para o Google Drive
|
26
|
+
- Criação e listagem de pastas
|
27
|
+
- Autenticação usando conta de serviço do Google
|
28
|
+
- Operações de compartilhamento e permissões
|
29
|
+
|
30
|
+
Note:
|
31
|
+
Como implementa Singleton, todas as instâncias criadas retornarão
|
32
|
+
a mesma referência de objeto, compartilhando estado e configurações.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
_instance (Optional[GoogleDrive]): Instância única da classe (padrão Singleton)
|
36
|
+
service: Cliente autenticado da API do Google Drive
|
37
|
+
version (str): Versão da API do Google Drive sendo utilizada
|
38
|
+
scopes (List[str]): Escopos de permissão para acesso ao Google Drive
|
39
|
+
with_subject (str): Email do usuário para delegação de credenciais
|
40
|
+
page_size (int): Número máximo de itens retornados por página nas consultas
|
41
|
+
|
42
|
+
Examples:
|
43
|
+
>>> # Primeira instância
|
44
|
+
>>> drive1 = GoogleDrive(token=service_account_info, with_subject="user@domain.com")
|
45
|
+
>>>
|
46
|
+
>>> # Segunda instância (retorna a mesma referência)
|
47
|
+
>>> drive2 = GoogleDrive(token=other_token, with_subject="other@domain.com")
|
48
|
+
>>> print(drive1 is drive2) # True - mesma instância
|
23
49
|
"""
|
24
50
|
|
25
|
-
_instance = None # Atributo de classe para armazenar a única instância
|
51
|
+
_instance: Optional['GoogleDrive'] = None # Atributo de classe para armazenar a única instância
|
26
52
|
|
27
|
-
def __new__(cls, *args, **kwargs):
|
28
|
-
|
53
|
+
def __new__(cls, *args, **kwargs) -> 'GoogleDrive':
|
54
|
+
"""
|
55
|
+
Implementa o padrão Singleton para garantir instância única.
|
56
|
+
|
57
|
+
Este método garante que apenas uma instância da classe GoogleDrive
|
58
|
+
seja criada durante toda a execução da aplicação, independentemente
|
59
|
+
de quantas vezes o construtor seja chamado.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
*args: Argumentos posicionais passados para o construtor
|
63
|
+
**kwargs: Argumentos nomeados passados para o construtor
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
GoogleDrive: A única instância da classe GoogleDrive
|
67
|
+
|
68
|
+
Note:
|
69
|
+
Na primeira chamada, uma nova instância é criada e armazenada.
|
70
|
+
Nas chamadas subsequentes, a mesma instância é retornada.
|
71
|
+
"""
|
72
|
+
# Verifica se já existe uma instância da classe
|
29
73
|
if cls._instance is None:
|
30
|
-
# Cria e armazena a instância na primeira chamada
|
74
|
+
# Cria e armazena a instância na primeira chamada
|
31
75
|
cls._instance = super().__new__(cls)
|
32
76
|
return cls._instance
|
33
77
|
|
34
78
|
def __init__(
|
35
79
|
self,
|
36
|
-
token:
|
80
|
+
token: Dict[str, Any],
|
37
81
|
with_subject: str,
|
38
|
-
scopes:
|
82
|
+
scopes: List[str] = ["https://www.googleapis.com/auth/drive"],
|
39
83
|
version: str = "v3",
|
40
|
-
):
|
84
|
+
) -> None:
|
41
85
|
"""
|
42
|
-
Inicializa uma instância da classe GoogleDrive.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
86
|
+
Inicializa uma instância da classe GoogleDrive com autenticação de conta de serviço.
|
87
|
+
|
88
|
+
Configura a autenticação usando uma conta de serviço do Google Cloud e
|
89
|
+
estabelece conexão com a API do Google Drive. A autenticação é delegada
|
90
|
+
para um usuário específico usando o parâmetro with_subject.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
token (Dict[str, Any]): Dicionário contendo as credenciais da conta de serviço.
|
94
|
+
Deve incluir campos como 'type', 'project_id', 'private_key_id',
|
95
|
+
'private_key', 'client_email', 'client_id', 'auth_uri', 'token_uri', etc.
|
96
|
+
with_subject (str): Email do usuário para o qual as credenciais serão delegadas.
|
97
|
+
Este usuário deve ter permissões adequadas no domínio da organização.
|
98
|
+
scopes (List[str], optional): Lista de escopos de permissão para acesso ao Google Drive.
|
99
|
+
Padrão: ["https://www.googleapis.com/auth/drive"] (acesso completo).
|
100
|
+
Outros exemplos:
|
101
|
+
- "https://www.googleapis.com/auth/drive.readonly" (somente leitura)
|
102
|
+
- "https://www.googleapis.com/auth/drive.file" (apenas arquivos criados pela app)
|
103
|
+
version (str, optional): Versão da API do Google Drive a ser utilizada.
|
104
|
+
Padrão: "v3" (versão mais recente estável).
|
105
|
+
|
48
106
|
Raises:
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
107
|
+
ValueError: Se os dados de entrada falharem na validação do Pydantic.
|
108
|
+
Inclui detalhes sobre quais campos são inválidos e por quê.
|
109
|
+
|
110
|
+
Attributes:
|
111
|
+
__token (Dict[str, Any]): Credenciais da conta de serviço (privado)
|
112
|
+
version (str): Versão da API sendo utilizada
|
113
|
+
scopes (List[str]): Escopos de permissão configurados
|
114
|
+
with_subject (str): Email do usuário delegado
|
115
|
+
page_size (int): Limite de itens por página (padrão: 1000)
|
116
|
+
service: Cliente autenticado da API do Google Drive
|
117
|
+
|
118
|
+
Examples:
|
119
|
+
>>> # Configuração básica
|
120
|
+
>>> token = {
|
121
|
+
... "type": "service_account",
|
122
|
+
... "project_id": "my-project",
|
123
|
+
... "private_key": "-----BEGIN PRIVATE KEY-----\n...",
|
124
|
+
... "client_email": "service@my-project.iam.gserviceaccount.com",
|
125
|
+
... # ... outros campos obrigatórios
|
126
|
+
... }
|
127
|
+
>>> drive = GoogleDrive(
|
128
|
+
... token=token,
|
129
|
+
... with_subject="user@domain.com"
|
130
|
+
... )
|
131
|
+
>>>
|
132
|
+
>>> # Configuração com escopos customizados
|
133
|
+
>>> drive = GoogleDrive(
|
134
|
+
... token=token,
|
135
|
+
... with_subject="admin@domain.com",
|
136
|
+
... scopes=["https://www.googleapis.com/auth/drive.readonly"],
|
137
|
+
... version="v3"
|
138
|
+
... )
|
54
139
|
"""
|
55
140
|
|
56
141
|
try:
|
142
|
+
# Validação dos parâmetros usando Pydantic para garantir tipos e formatos corretos
|
57
143
|
InitParamsValidator(
|
58
144
|
token=token, with_subject=with_subject, scopes=scopes, version=version
|
59
145
|
)
|
@@ -62,84 +148,186 @@ class GoogleDrive:
|
|
62
148
|
"Erro na validação dos dados de input da inicialização da instância:",
|
63
149
|
e.errors(),
|
64
150
|
)
|
65
|
-
|
66
|
-
|
67
|
-
self.
|
68
|
-
self.
|
69
|
-
self.
|
151
|
+
|
152
|
+
# Armazenamento das configurações validadas
|
153
|
+
self.__token = token # Credenciais privadas da conta de serviço
|
154
|
+
self.version = version # Versão da API do Google Drive
|
155
|
+
self.scopes = scopes # Escopos de permissão para autenticação
|
156
|
+
self.with_subject = with_subject # Email do usuário para delegação
|
157
|
+
self.page_size = 1000 # Limite padrão de itens por página nas consultas
|
158
|
+
|
159
|
+
# Criação do cliente autenticado da API do Google Drive
|
70
160
|
self.service = self.__create_service()
|
71
161
|
|
72
|
-
def __create_service(self):
|
162
|
+
def __create_service(self) -> Union[Any, bool]:
|
73
163
|
"""
|
74
|
-
Cria
|
164
|
+
Cria e configura o cliente de serviço do Google Drive.
|
165
|
+
|
166
|
+
Este método privado estabelece a conexão autenticada com a API do Google Drive
|
167
|
+
usando as credenciais da conta de serviço configuradas durante a inicialização.
|
168
|
+
O processo inclui autenticação delegada e construção do objeto de serviço.
|
169
|
+
|
75
170
|
Returns:
|
76
|
-
|
77
|
-
|
171
|
+
Union[Any, bool]:
|
172
|
+
- Objeto do serviço Google Drive se a conexão for bem-sucedida
|
173
|
+
- False se ocorrer qualquer erro durante o processo de criação
|
174
|
+
|
78
175
|
Raises:
|
79
|
-
Exception:
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
176
|
+
Exception: Capturada internamente e retorna False em caso de erro.
|
177
|
+
Possíveis causas de erro incluem:
|
178
|
+
- Credenciais inválidas ou malformadas
|
179
|
+
- Problemas de conectividade de rede
|
180
|
+
- Configurações incorretas de escopo ou versão da API
|
181
|
+
- Falhas na delegação de credenciais
|
182
|
+
|
183
|
+
Note:
|
184
|
+
Este é um método privado (prefixo __) e não deve ser chamado diretamente
|
185
|
+
fora da classe. É usado automaticamente durante a inicialização.
|
186
|
+
|
187
|
+
Examples:
|
188
|
+
>>> # Uso interno durante __init__
|
189
|
+
>>> self.service = self.__create_service()
|
190
|
+
>>> if self.service:
|
191
|
+
... print("Conexão com Google Drive estabelecida")
|
192
|
+
... else:
|
193
|
+
... print("Falha na conexão com Google Drive")
|
86
194
|
"""
|
87
195
|
|
88
196
|
try:
|
197
|
+
# Autentica usando as credenciais da conta de serviço com delegação de usuário
|
89
198
|
auth = self.__autentica(self.with_subject)
|
199
|
+
|
200
|
+
# Constrói o cliente da API do Google Drive com as credenciais autenticadas
|
90
201
|
service = build(f"drive", f"{self.version}", credentials=auth)
|
91
202
|
return service
|
92
203
|
except Exception as e:
|
204
|
+
# Em caso de erro, retorna False para indicar falha na criação do serviço
|
205
|
+
# O erro específico é capturado mas não re-lançado para manter a interface limpa
|
93
206
|
return False
|
94
207
|
|
95
|
-
def __autentica(self, with_subject: str):
|
208
|
+
def __autentica(self, with_subject: str) -> Union[service_account.Credentials, bool]:
|
96
209
|
"""
|
97
|
-
|
210
|
+
Realiza autenticação delegada usando conta de serviço do Google Cloud.
|
211
|
+
|
212
|
+
Este método privado configura a autenticação OAuth2 usando uma conta de serviço
|
213
|
+
e delega as credenciais para um usuário específico. É necessário quando uma
|
214
|
+
aplicação precisa acessar recursos do Google Drive em nome de um usuário
|
215
|
+
específico da organização.
|
216
|
+
|
98
217
|
Args:
|
99
|
-
with_subject (str):
|
218
|
+
with_subject (str): Email do usuário para o qual as credenciais serão delegadas.
|
219
|
+
Este usuário deve existir no domínio da organização e ter as permissões
|
220
|
+
adequadas para acessar os recursos solicitados.
|
221
|
+
|
100
222
|
Returns:
|
101
|
-
|
223
|
+
Union[service_account.Credentials, bool]:
|
224
|
+
- Objeto de credenciais delegadas se a autenticação for bem-sucedida
|
225
|
+
- False se ocorrer qualquer erro durante o processo de autenticação
|
226
|
+
|
102
227
|
Raises:
|
103
|
-
Exception:
|
104
|
-
|
105
|
-
|
106
|
-
|
228
|
+
Exception: Capturada internamente e retorna False em caso de erro.
|
229
|
+
Possíveis causas de erro incluem:
|
230
|
+
- Token de conta de serviço inválido ou malformado
|
231
|
+
- Usuário especificado não existe no domínio
|
232
|
+
- Escopos insuficientes para a delegação
|
233
|
+
- Conta de serviço sem permissão de delegação
|
234
|
+
|
235
|
+
Note:
|
236
|
+
- Este é um método privado (prefixo __) usado internamente pela classe
|
237
|
+
- Requer que a conta de serviço tenha permissão de delegação configurada
|
238
|
+
- O usuário delegado deve fazer parte do mesmo domínio da organização
|
239
|
+
|
240
|
+
Examples:
|
241
|
+
>>> # Uso interno - autentica para um usuário específico
|
242
|
+
>>> credentials = self.__autentica("user@company.com")
|
243
|
+
>>> if credentials:
|
244
|
+
... print("Autenticação delegada bem-sucedida")
|
245
|
+
... else:
|
246
|
+
... print("Falha na autenticação delegada")
|
107
247
|
"""
|
108
248
|
|
109
249
|
try:
|
250
|
+
# Cria credenciais da conta de serviço a partir do token fornecido
|
110
251
|
credentials = service_account.Credentials.from_service_account_info(
|
111
252
|
self.__token, scopes=self.scopes
|
112
253
|
)
|
254
|
+
|
255
|
+
# Delega as credenciais para o usuário especificado
|
256
|
+
# Isso permite que a aplicação aja em nome do usuário
|
113
257
|
delegated_credencial = credentials.with_subject(with_subject)
|
114
258
|
return delegated_credencial
|
115
259
|
|
116
260
|
except Exception as e:
|
261
|
+
# Em caso de erro, retorna False para indicar falha na autenticação
|
262
|
+
# O erro específico é capturado mas não re-lançado para manter a interface limpa
|
117
263
|
return False
|
118
264
|
|
119
|
-
def upload(self, folder_id: str, name: str, file_path: str, mimetype: str):
|
265
|
+
def upload(self, folder_id: str, name: str, file_path: str, mimetype: str) -> Optional[Dict[str, Any]]:
|
120
266
|
"""
|
121
|
-
|
267
|
+
Realiza upload de um arquivo para uma pasta específica no Google Drive.
|
268
|
+
|
269
|
+
Este método faz upload de arquivos locais para o Google Drive, criando uma nova
|
270
|
+
entrada na pasta especificada. Suporta todos os tipos de arquivo através da
|
271
|
+
especificação do tipo MIME apropriado.
|
122
272
|
|
123
273
|
Args:
|
124
|
-
folder_id (str): ID da pasta no Google Drive onde o arquivo será armazenado.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
274
|
+
folder_id (str): ID único da pasta no Google Drive onde o arquivo será armazenado.
|
275
|
+
Formato típico: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
|
276
|
+
name (str): Nome que o arquivo terá no Google Drive (pode ser diferente do nome local).
|
277
|
+
Exemplo: "documento_final.pdf", "relatorio_2025.xlsx"
|
278
|
+
file_path (str): Caminho absoluto ou relativo para o arquivo no sistema local.
|
279
|
+
Exemplo: "/home/user/documents/file.pdf", "C:\\Users\\user\\file.xlsx"
|
280
|
+
mimetype (str): Tipo MIME do arquivo que define como ele será interpretado.
|
281
|
+
Exemplos comuns:
|
282
|
+
- "text/plain" - Arquivo de texto simples
|
283
|
+
- "text/html" - Arquivo HTML
|
284
|
+
- "image/jpeg" - Imagem JPEG
|
285
|
+
- "image/png" - Imagem PNG
|
286
|
+
- "audio/mpeg" - Arquivo de áudio MP3
|
287
|
+
- "video/mp4" - Arquivo de vídeo MP4
|
288
|
+
- "application/pdf" - Documento PDF
|
289
|
+
- "application/vnd.ms-excel" - Planilha Excel
|
290
|
+
- "application/octet-stream" - Arquivo binário genérico
|
137
291
|
|
138
292
|
Returns:
|
139
|
-
|
140
|
-
|
293
|
+
Optional[Dict[str, Any]]:
|
294
|
+
- Dicionário com informações do arquivo carregado se bem-sucedido:
|
295
|
+
{"id": "arquivo_id", "name": "nome_arquivo", "parents": ["pasta_id"], ...}
|
296
|
+
- None se o arquivo local não for encontrado
|
297
|
+
|
298
|
+
Raises:
|
299
|
+
ValueError: Se os parâmetros de entrada falharem na validação do Pydantic.
|
300
|
+
Inclui detalhes sobre quais campos são inválidos.
|
301
|
+
HttpError: Se ocorrer erro na comunicação com a API do Google Drive.
|
302
|
+
Pode indicar problemas de permissão, quota ou conectividade.
|
303
|
+
|
304
|
+
Examples:
|
305
|
+
>>> # Upload de um documento PDF
|
306
|
+
>>> resultado = drive.upload(
|
307
|
+
... folder_id="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
|
308
|
+
... name="contrato_assinado.pdf",
|
309
|
+
... file_path="/documents/contrato.pdf",
|
310
|
+
... mimetype="application/pdf"
|
311
|
+
... )
|
312
|
+
>>> if resultado:
|
313
|
+
... print(f"Arquivo carregado com ID: {resultado['id']}")
|
314
|
+
|
315
|
+
>>> # Upload de uma imagem
|
316
|
+
>>> resultado = drive.upload(
|
317
|
+
... folder_id="pasta_fotos_id",
|
318
|
+
... name="foto_evento.jpg",
|
319
|
+
... file_path="C:\\fotos\\evento.jpg",
|
320
|
+
... mimetype="image/jpeg"
|
321
|
+
... )
|
322
|
+
|
323
|
+
Note:
|
324
|
+
- O arquivo deve existir no caminho especificado antes do upload
|
325
|
+
- O folder_id deve referenciar uma pasta existente e acessível
|
326
|
+
- O tipo MIME deve corresponder ao tipo real do arquivo para funcionamento adequado
|
327
|
+
- Arquivos com mesmo nome na mesma pasta não são sobrescritos, são duplicados
|
141
328
|
"""
|
142
329
|
try:
|
330
|
+
# Validação dos parâmetros usando Pydantic para garantir tipos corretos
|
143
331
|
UploadValidator(
|
144
332
|
folder_id=folder_id, name=name, file_path=file_path, mimetype=mimetype
|
145
333
|
)
|
@@ -149,92 +337,166 @@ class GoogleDrive:
|
|
149
337
|
e.errors(),
|
150
338
|
)
|
151
339
|
|
340
|
+
# Metadados do arquivo: nome e pasta de destino no Google Drive
|
152
341
|
file_metadata = {"name": name, "parents": [folder_id]}
|
342
|
+
|
343
|
+
# Verifica se o arquivo existe no sistema local antes de tentar upload
|
153
344
|
if not os.path.exists(file_path):
|
154
|
-
return
|
155
|
-
"success": False,
|
156
|
-
"result": None,
|
157
|
-
"error": "Diretório ou arquivo não encontrado",
|
158
|
-
}
|
345
|
+
return None # Retorna None se arquivo não encontrado
|
159
346
|
|
160
347
|
try:
|
348
|
+
# Configura o objeto de mídia para upload com suporte a resumo
|
161
349
|
media = MediaFileUpload(file_path, mimetype=mimetype, resumable=True)
|
350
|
+
|
351
|
+
# Executa o upload do arquivo para o Google Drive
|
162
352
|
file = (
|
163
353
|
self.service.files()
|
164
354
|
.create(
|
165
|
-
body=file_metadata,
|
166
|
-
media_body=media,
|
167
|
-
fields="id",
|
168
|
-
supportsAllDrives=True,
|
355
|
+
body=file_metadata, # Metadados (nome, pasta pai)
|
356
|
+
media_body=media, # Conteúdo do arquivo
|
357
|
+
fields="id", # Campos a retornar (apenas ID para eficiência)
|
358
|
+
supportsAllDrives=True, # Suporte a drives compartilhados
|
169
359
|
)
|
170
360
|
.execute()
|
171
361
|
)
|
172
362
|
|
363
|
+
# Retorna informações do arquivo carregado em formato padronizado
|
173
364
|
return {"success": True, "result": file}
|
174
365
|
except Exception as e:
|
175
|
-
|
366
|
+
# Em caso de erro, retorna informações do erro em formato padronizado
|
176
367
|
return {"success": False, "result": None, "error": str(e)}
|
177
368
|
|
178
|
-
def _validate_folder_existence(self, folder: str, id_folder: str):
|
369
|
+
def _validate_folder_existence(self, folder: str, id_folder: str) -> Optional[Dict[str, Any]]:
|
179
370
|
"""
|
180
|
-
Verifica
|
371
|
+
Verifica se uma pasta específica existe dentro de uma pasta pai no Google Drive.
|
372
|
+
|
373
|
+
Este método privado busca por uma pasta com nome específico dentro de uma pasta
|
374
|
+
pai identificada por seu ID. É usado internamente para evitar duplicação de
|
375
|
+
pastas antes de criar novas.
|
376
|
+
|
181
377
|
Args:
|
182
|
-
folder (str):
|
183
|
-
|
378
|
+
folder (str): Nome exato da pasta a ser verificada.
|
379
|
+
Exemplo: "Documentos 2025", "Relatórios Mensais"
|
380
|
+
id_folder (str): ID da pasta pai onde buscar.
|
381
|
+
Formato típico: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
|
382
|
+
|
184
383
|
Returns:
|
185
|
-
|
384
|
+
Optional[Dict[str, Any]]:
|
385
|
+
- Dicionário com informações da pasta se encontrada:
|
386
|
+
{"id": "pasta_id", "name": "nome_pasta", "mimeType": "application/vnd.google-apps.folder"}
|
387
|
+
- None se a pasta não for encontrada na pasta pai
|
388
|
+
|
186
389
|
Raises:
|
187
|
-
ValueError: Se ocorrer
|
390
|
+
ValueError: Se ocorrer erro durante a busca pela pasta.
|
391
|
+
Pode indicar problemas de conectividade, permissões ou IDs inválidos.
|
392
|
+
|
393
|
+
Note:
|
394
|
+
- Este é um método privado (prefixo _) usado internamente pela classe
|
395
|
+
- A busca é case-sensitive (sensível a maiúsculas/minúsculas)
|
396
|
+
- Apenas pastas não movidas para lixeira são consideradas
|
397
|
+
- Suporta drives compartilhados da organização
|
398
|
+
|
399
|
+
Examples:
|
400
|
+
>>> # Busca pasta "Relatórios" dentro da pasta pai
|
401
|
+
>>> pasta = self._validate_folder_existence("Relatórios", "parent_folder_id")
|
402
|
+
>>> if pasta:
|
403
|
+
... print(f"Pasta encontrada: {pasta['id']}")
|
404
|
+
... else:
|
405
|
+
... print("Pasta não existe")
|
188
406
|
"""
|
189
|
-
|
407
|
+
|
408
|
+
# Query para buscar itens na pasta pai que não estão na lixeira
|
190
409
|
query = f"'{id_folder}' in parents and trashed=false"
|
191
410
|
|
192
411
|
try:
|
193
|
-
|
412
|
+
# Executa busca na API do Google Drive
|
194
413
|
response = (
|
195
414
|
self.service.files()
|
196
415
|
.list(
|
197
|
-
q=query,
|
198
|
-
spaces="drive",
|
199
|
-
fields="nextPageToken, files(id, name, mimeType)",
|
200
|
-
pageToken=None,
|
201
|
-
includeItemsFromAllDrives=True,
|
202
|
-
supportsAllDrives=True,
|
416
|
+
q=query, # Query de busca
|
417
|
+
spaces="drive", # Espaço de busca (drive principal)
|
418
|
+
fields="nextPageToken, files(id, name, mimeType)", # Campos retornados
|
419
|
+
pageToken=None, # Token de paginação (primeira página)
|
420
|
+
includeItemsFromAllDrives=True, # Incluir drives compartilhados
|
421
|
+
supportsAllDrives=True, # Suporte a drives compartilhados
|
203
422
|
)
|
204
423
|
.execute()
|
205
424
|
)
|
206
425
|
|
426
|
+
# Extrai lista de arquivos/pastas da resposta
|
207
427
|
items = response.get("files", [])
|
208
428
|
|
429
|
+
# Procura por pasta com nome específico
|
209
430
|
for item in items:
|
210
|
-
|
431
|
+
# Verifica se é uma pasta (tipo MIME específico) e nome corresponde
|
211
432
|
if (
|
212
433
|
item["mimeType"] == "application/vnd.google-apps.folder"
|
213
434
|
and item["name"] == folder
|
214
435
|
):
|
436
|
+
return item # Retorna informações da pasta encontrada
|
215
437
|
|
216
|
-
|
217
|
-
|
218
|
-
return None
|
438
|
+
return None # Pasta não encontrada
|
219
439
|
|
220
440
|
except Exception as e:
|
221
|
-
|
222
|
-
raise ValueError(f"Erro tentando procurar pela pasta:{e}")
|
441
|
+
# Lança erro com contexto específico sobre a falha na busca
|
442
|
+
raise ValueError(f"Erro tentando procurar pela pasta: {e}")
|
223
443
|
|
224
444
|
def create_folder(
|
225
445
|
self, name: str, parent_folder_id: str, validate_existence: bool = False
|
226
|
-
):
|
446
|
+
) -> Dict[str, Any]:
|
227
447
|
"""
|
228
|
-
Cria uma pasta no Google Drive dentro de uma pasta
|
448
|
+
Cria uma nova pasta no Google Drive dentro de uma pasta pai especificada.
|
449
|
+
|
450
|
+
Este método permite criar pastas no Google Drive com opção de verificar
|
451
|
+
se a pasta já existe antes da criação. É útil para organizar arquivos
|
452
|
+
em estruturas hierárquicas de pastas.
|
229
453
|
|
230
454
|
Args:
|
231
455
|
name (str): Nome da pasta a ser criada.
|
232
|
-
|
233
|
-
|
456
|
+
Exemplo: "Relatórios 2025", "Documentos Importantes"
|
457
|
+
parent_folder_id (str): ID da pasta pai onde a nova pasta será criada.
|
458
|
+
Formato típico: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
|
459
|
+
Use "root" para criar na raiz do drive
|
460
|
+
validate_existence (bool, optional): Se True, verifica se a pasta já existe
|
461
|
+
antes de tentar criar uma nova. Se a pasta existir, retorna suas
|
462
|
+
informações ao invés de criar duplicata. Padrão: False.
|
463
|
+
|
234
464
|
Returns:
|
235
|
-
str:
|
465
|
+
Dict[str, Any]: Dicionário padronizado com resultado da operação:
|
466
|
+
- success (bool): True se operação bem-sucedida, False caso contrário
|
467
|
+
- result (Dict[str, Any] | None): Informações da pasta criada/encontrada
|
468
|
+
incluindo ID, nome e outros metadados
|
469
|
+
- error (str | None): Mensagem de erro se success=False
|
470
|
+
|
471
|
+
Raises:
|
472
|
+
ValueError: Se os parâmetros de entrada falharem na validação do Pydantic.
|
473
|
+
Inclui detalhes sobre quais campos são inválidos.
|
474
|
+
|
475
|
+
Examples:
|
476
|
+
>>> # Criar pasta sem verificar existência
|
477
|
+
>>> resultado = drive.create_folder(
|
478
|
+
... name="Nova Pasta",
|
479
|
+
... parent_folder_id="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
|
480
|
+
... )
|
481
|
+
>>> if resultado["success"]:
|
482
|
+
... print(f"Pasta criada com ID: {resultado['result']['id']}")
|
483
|
+
|
484
|
+
>>> # Criar pasta verificando se já existe
|
485
|
+
>>> resultado = drive.create_folder(
|
486
|
+
... name="Documentos",
|
487
|
+
... parent_folder_id="root",
|
488
|
+
... validate_existence=True
|
489
|
+
... )
|
490
|
+
>>> print(f"Status: {resultado['success']}")
|
491
|
+
|
492
|
+
Note:
|
493
|
+
- Se validate_existence=True e a pasta existir, não será criada duplicata
|
494
|
+
- Nomes de pastas são case-sensitive no Google Drive
|
495
|
+
- A pasta pai deve existir e ser acessível com as credenciais atuais
|
496
|
+
- Suporta drives compartilhados da organização
|
236
497
|
"""
|
237
498
|
try:
|
499
|
+
# Validação dos parâmetros usando Pydantic
|
238
500
|
CreateFolderValidator(
|
239
501
|
name=name,
|
240
502
|
parent_folder_id=parent_folder_id,
|
@@ -242,33 +504,41 @@ class GoogleDrive:
|
|
242
504
|
)
|
243
505
|
except ValidationError as e:
|
244
506
|
raise ValueError(
|
245
|
-
"Erro na validação dos dados de input da
|
507
|
+
"Erro na validação dos dados de input da criação da pasta:",
|
246
508
|
e.errors(),
|
247
509
|
)
|
248
510
|
|
249
511
|
status_existence = None
|
250
512
|
|
513
|
+
# Verifica existência da pasta se solicitado
|
251
514
|
if validate_existence:
|
252
|
-
|
253
515
|
status_existence = self._validate_folder_existence(name, parent_folder_id)
|
254
516
|
|
517
|
+
# Cria pasta apenas se não existir (ou se verificação não foi solicitada)
|
255
518
|
if status_existence is None:
|
256
|
-
|
257
519
|
try:
|
520
|
+
# Metadados da nova pasta
|
258
521
|
folder_metadata = {
|
259
522
|
"name": name,
|
260
523
|
"parents": [parent_folder_id],
|
261
|
-
"mimeType": "application/vnd.google-apps.folder",
|
524
|
+
"mimeType": "application/vnd.google-apps.folder", # Tipo MIME para pastas
|
262
525
|
}
|
526
|
+
|
527
|
+
# Executa criação da pasta via API
|
263
528
|
folder = (
|
264
529
|
self.service.files()
|
265
|
-
.create(
|
530
|
+
.create(
|
531
|
+
body=folder_metadata,
|
532
|
+
fields="id",
|
533
|
+
supportsAllDrives=True # Suporte a drives compartilhados
|
534
|
+
)
|
266
535
|
.execute()
|
267
536
|
)
|
268
537
|
return {"success": True, "result": folder}
|
269
538
|
except Exception as e:
|
270
539
|
return {"success": False, "result": None, "error": str(e)}
|
271
540
|
|
541
|
+
# Retorna pasta existente se encontrada
|
272
542
|
return {"success": True, "result": status_existence}
|
273
543
|
|
274
544
|
def list_items_folder(
|
@@ -276,46 +546,104 @@ class GoogleDrive:
|
|
276
546
|
query: str = "",
|
277
547
|
spaces: str = "drive",
|
278
548
|
fields: str = "nextPageToken, files(id, name)",
|
279
|
-
):
|
549
|
+
) -> Dict[str, Any]:
|
280
550
|
"""
|
281
|
-
Lista
|
551
|
+
Lista arquivos e pastas no Google Drive com base em critérios de busca especificados.
|
552
|
+
|
553
|
+
Este método permite buscar e listar conteúdos do Google Drive usando queries
|
554
|
+
personalizadas. Suporta busca em drives pessoais e compartilhados, com
|
555
|
+
controle sobre quais campos são retornados para otimizar performance.
|
282
556
|
|
283
557
|
Args:
|
284
|
-
query (str, optional): Critério de busca para
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
558
|
+
query (str, optional): Critério de busca para filtrar arquivos e pastas.
|
559
|
+
Padrão: "" (lista todos os itens acessíveis).
|
560
|
+
Exemplos de queries:
|
561
|
+
- "'1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms' in parents and trashed=false"
|
562
|
+
(itens em pasta específica, não na lixeira)
|
563
|
+
- "name contains 'relatório'" (arquivos com 'relatório' no nome)
|
564
|
+
- "mimeType='application/pdf'" (apenas arquivos PDF)
|
565
|
+
- "modifiedTime > '2025-01-01T00:00:00'" (modificados após data)
|
566
|
+
Consulte: https://developers.google.com/drive/api/v3/ref-search-terms
|
567
|
+
|
568
|
+
spaces (str, optional): Especifica locais de armazenamento para consulta.
|
569
|
+
Padrão: "drive".
|
570
|
+
Opções disponíveis:
|
571
|
+
- "drive": Drive principal do usuário
|
572
|
+
- "appDataFolder": Pasta de dados da aplicação
|
573
|
+
- "photos": Google Fotos (se aplicável)
|
574
|
+
|
575
|
+
fields (str, optional): Campos a serem retornados na resposta da API.
|
576
|
+
Padrão: "nextPageToken, files(id, name)".
|
577
|
+
Exemplos de campos:
|
578
|
+
- "files(id, name, size, modifiedTime)" (informações básicas + tamanho/data)
|
579
|
+
- "files(id, name, mimeType, parents)" (inclui tipo MIME e pastas pai)
|
580
|
+
- "nextPageToken, files(*)" (todos os campos disponíveis)
|
581
|
+
Consulte: https://developers.google.com/drive/api/v3/reference/files
|
292
582
|
|
293
583
|
Returns:
|
294
|
-
|
584
|
+
Dict[str, Any]: Dicionário padronizado com resultado da operação:
|
585
|
+
- success (bool): True se operação bem-sucedida, False caso contrário
|
586
|
+
- result (Dict[str, Any] | None): Dados retornados da API se success=True:
|
587
|
+
* files (List[Dict]): Lista de arquivos/pastas encontrados
|
588
|
+
* nextPageToken (str): Token para próxima página (se houver)
|
589
|
+
- error (str | None): Mensagem de erro se success=False
|
590
|
+
|
591
|
+
Raises:
|
592
|
+
ValueError: Se os parâmetros de entrada falharem na validação do Pydantic.
|
593
|
+
|
594
|
+
Examples:
|
595
|
+
>>> # Listar todos os itens na raiz do drive
|
596
|
+
>>> resultado = drive.list_items_folder()
|
597
|
+
>>> if resultado["success"]:
|
598
|
+
... arquivos = resultado["result"]["files"]
|
599
|
+
... for arquivo in arquivos:
|
600
|
+
... print(f"Nome: {arquivo['name']}, ID: {arquivo['id']}")
|
601
|
+
|
602
|
+
>>> # Listar apenas PDFs em uma pasta específica
|
603
|
+
>>> resultado = drive.list_items_folder(
|
604
|
+
... query="'pasta_id' in parents and mimeType='application/pdf'",
|
605
|
+
... fields="files(id, name, size, modifiedTime)"
|
606
|
+
... )
|
607
|
+
|
608
|
+
>>> # Buscar arquivos por nome
|
609
|
+
>>> resultado = drive.list_items_folder(
|
610
|
+
... query="name contains 'contrato' and trashed=false"
|
611
|
+
... )
|
612
|
+
|
613
|
+
Note:
|
614
|
+
- Resultados são limitados pelo page_size configurado na inicialização
|
615
|
+
- Use nextPageToken para navegar entre páginas de resultados extensos
|
616
|
+
- Queries são case-insensitive para busca por nome
|
617
|
+
- Suporta drives compartilhados automaticamente
|
295
618
|
"""
|
296
619
|
try:
|
620
|
+
# Validação dos parâmetros usando Pydantic
|
297
621
|
ListFolderValidator(query=query, fields=fields, spaces=spaces)
|
298
622
|
except ValidationError as e:
|
299
623
|
raise ValueError(
|
300
624
|
"Erro na validação dos dados de input da lista:", e.errors()
|
301
625
|
)
|
626
|
+
|
302
627
|
try:
|
628
|
+
# Executa consulta na API do Google Drive
|
303
629
|
results = (
|
304
630
|
self.service.files()
|
305
631
|
.list(
|
306
|
-
q=query,
|
307
|
-
spaces=spaces,
|
308
|
-
pageSize=self.page_size,
|
309
|
-
fields=fields,
|
310
|
-
supportsAllDrives=True,
|
311
|
-
includeItemsFromAllDrives=True,
|
632
|
+
q=query, # Query de busca
|
633
|
+
spaces=spaces, # Espaços de armazenamento
|
634
|
+
pageSize=self.page_size, # Limite de itens por página
|
635
|
+
fields=fields, # Campos a retornar
|
636
|
+
supportsAllDrives=True, # Suporte a drives compartilhados
|
637
|
+
includeItemsFromAllDrives=True, # Incluir itens de drives compartilhados
|
312
638
|
)
|
313
639
|
.execute()
|
314
640
|
)
|
315
641
|
return {"success": True, "result": results}
|
316
642
|
except HttpError as hr:
|
643
|
+
# Erro específico da API HTTP (ex: 403 Forbidden, 404 Not Found)
|
317
644
|
return {"success": False, "result": None, "error": str(hr)}
|
318
645
|
except Exception as e:
|
646
|
+
# Outros erros gerais (rede, parsing, etc.)
|
319
647
|
return {"success": False, "result": None, "error": str(e)}
|
320
648
|
|
321
649
|
def download_google_files(self, file: str, mimeType: str, path: str):
|
@@ -442,4 +770,221 @@ class GoogleDrive:
|
|
442
770
|
self.service.files().delete(fileId=id_file, supportsAllDrives=True).execute()
|
443
771
|
return {"success": True}
|
444
772
|
except Exception as e:
|
445
|
-
return {"success": False, "error": str(e)}
|
773
|
+
return {"success": False, "error": str(e)}
|
774
|
+
|
775
|
+
def _find_file_in_folder_by_name(self, parent_folder_id: str, name: str) -> Optional[dict]:
|
776
|
+
"""
|
777
|
+
Retorna o primeiro arquivo (não excluído) com 'name' dentro de 'parent_folder_id',
|
778
|
+
ou None se não existir.
|
779
|
+
"""
|
780
|
+
# Escapa aspas simples no nome para a query
|
781
|
+
safe_name = name.replace("'", r"\'")
|
782
|
+
q = (
|
783
|
+
f"'{parent_folder_id}' in parents and "
|
784
|
+
f"name = '{safe_name}' and "
|
785
|
+
f"trashed = false"
|
786
|
+
)
|
787
|
+
try:
|
788
|
+
resp = (
|
789
|
+
self.service.files()
|
790
|
+
.list(
|
791
|
+
q=q,
|
792
|
+
spaces="drive",
|
793
|
+
pageSize=1,
|
794
|
+
fields="files(id, name, mimeType, size, webViewLink, parents)",
|
795
|
+
supportsAllDrives=True,
|
796
|
+
includeItemsFromAllDrives=True,
|
797
|
+
)
|
798
|
+
.execute()
|
799
|
+
)
|
800
|
+
files = resp.get("files", [])
|
801
|
+
return files[0] if files else None
|
802
|
+
except Exception as e:
|
803
|
+
# Padroniza retorno de erro
|
804
|
+
return None
|
805
|
+
|
806
|
+
def ensure_folder(self, parent_folder_id: str, name: str) -> str:
|
807
|
+
"""
|
808
|
+
Usa o método create_folder(validate_existence=True) para retornar o ID de 'name'
|
809
|
+
dentro de 'parent_folder_id', criando se não existir.
|
810
|
+
"""
|
811
|
+
resp = self.create_folder(
|
812
|
+
name=name,
|
813
|
+
parent_folder_id=parent_folder_id,
|
814
|
+
validate_existence=True,
|
815
|
+
)
|
816
|
+
|
817
|
+
if not resp or not resp.get("success"):
|
818
|
+
raise RuntimeError(f"Falha ao garantir pasta '{name}': {resp}")
|
819
|
+
# A classe retorna em "result" um dict com ao menos "id"
|
820
|
+
folder = resp["result"]
|
821
|
+
folder_id = folder.get("id")
|
822
|
+
if not folder_id:
|
823
|
+
# Algumas respostas podem ser {'success': True, 'result': {'id': '...'}} ou já o objeto
|
824
|
+
# Se por algum motivo vier diferente, trate aqui
|
825
|
+
raise RuntimeError(f"Resposta inesperada ao criar/buscar pasta: {folder}")
|
826
|
+
return folder_id
|
827
|
+
|
828
|
+
def ensure_path(self, path: str, parent_folder_id: str = "root") -> str:
|
829
|
+
"""
|
830
|
+
Percorre/cria cada nível de 'path' dentro de 'parent_folder_id'.
|
831
|
+
Retorna o ID da última pasta.
|
832
|
+
"""
|
833
|
+
current_parent = parent_folder_id
|
834
|
+
for part in [p for p in path.split("/") if p.strip()]:
|
835
|
+
current_parent = self.ensure_folder(current_parent, part)
|
836
|
+
return current_parent
|
837
|
+
|
838
|
+
def upload_pdf_to_drive_path(
|
839
|
+
self,
|
840
|
+
local_pdf_path: str,
|
841
|
+
drive_folder_path: str,
|
842
|
+
parent_folder_id: str = "root",
|
843
|
+
rename_as: Optional[str] = None,
|
844
|
+
) -> dict:
|
845
|
+
"""
|
846
|
+
- Garante a hierarquia 'drive_folder_path' (ex: "MeusPdfs/2025/09")
|
847
|
+
- Sobe o PDF com mimetype application/pdf
|
848
|
+
- Se rename_as for fornecido, usa esse nome no Drive (com .pdf); caso contrário, basename do arquivo local.
|
849
|
+
|
850
|
+
Retorna o dict padronizado da classe: {"success": True/False, "result": {...} | None, "error": str | None}
|
851
|
+
"""
|
852
|
+
if not os.path.exists(local_pdf_path):
|
853
|
+
return {"success": False, "result": None, "error": f"Arquivo não encontrado: {local_pdf_path}"}
|
854
|
+
|
855
|
+
folder_id = self.ensure_path(drive_folder_path, parent_folder_id=parent_folder_id)
|
856
|
+
|
857
|
+
# nome do arquivo no Drive
|
858
|
+
base = os.path.basename(local_pdf_path)
|
859
|
+
if rename_as:
|
860
|
+
name = rename_as if rename_as.lower().endswith(".pdf") else f"{rename_as}.pdf"
|
861
|
+
else:
|
862
|
+
name = base
|
863
|
+
|
864
|
+
# mimetype do PDF
|
865
|
+
mimetype = "application/pdf"
|
866
|
+
|
867
|
+
return self.upload(
|
868
|
+
folder_id=folder_id,
|
869
|
+
name=name,
|
870
|
+
file_path=local_pdf_path,
|
871
|
+
mimetype=mimetype,
|
872
|
+
)
|
873
|
+
|
874
|
+
def _gdrive_upload_or_update_media(
|
875
|
+
self,
|
876
|
+
folder_id: str,
|
877
|
+
name: str,
|
878
|
+
mimetype: str,
|
879
|
+
file_handle: BytesIO,
|
880
|
+
overwrite: bool = True,
|
881
|
+
) -> dict:
|
882
|
+
"""
|
883
|
+
Se overwrite=True, procura por (name, folder_id) e:
|
884
|
+
- se existir: faz update do conteúdo (mantém o mesmo fileId)
|
885
|
+
- se não existir: cria
|
886
|
+
Se overwrite=False, sempre cria (comportamento atual).
|
887
|
+
"""
|
888
|
+
try:
|
889
|
+
media = MediaIoBaseUpload(
|
890
|
+
file_handle,
|
891
|
+
mimetype=mimetype,
|
892
|
+
resumable=True,
|
893
|
+
chunksize=1024 * 1024,
|
894
|
+
)
|
895
|
+
if overwrite:
|
896
|
+
existing = self._find_file_in_folder_by_name(folder_id, name)
|
897
|
+
if existing:
|
898
|
+
file_id = existing["id"]
|
899
|
+
file = (
|
900
|
+
self.service.files()
|
901
|
+
.update(
|
902
|
+
fileId=file_id,
|
903
|
+
media_body=media,
|
904
|
+
fields="id, name, mimeType, size, webViewLink, parents",
|
905
|
+
supportsAllDrives=True,
|
906
|
+
)
|
907
|
+
.execute()
|
908
|
+
)
|
909
|
+
return {"success": True, "result": file, "error": None}
|
910
|
+
|
911
|
+
# cria se não houver existente ou overwrite=False
|
912
|
+
file_metadata = {"name": name, "parents": [folder_id]}
|
913
|
+
file = (
|
914
|
+
self.service.files()
|
915
|
+
.create(
|
916
|
+
body=file_metadata,
|
917
|
+
media_body=media,
|
918
|
+
fields="id, name, mimeType, size, webViewLink, parents",
|
919
|
+
supportsAllDrives=True,
|
920
|
+
)
|
921
|
+
.execute()
|
922
|
+
)
|
923
|
+
return {"success": True, "result": file, "error": None}
|
924
|
+
except Exception as e:
|
925
|
+
return {"success": False, "result": None, "error": str(e)}
|
926
|
+
|
927
|
+
def upload_pdf_bytes_to_drive_path(
|
928
|
+
self,
|
929
|
+
content: bytes,
|
930
|
+
drive_folder_path: str,
|
931
|
+
parent_folder_id: str = "root",
|
932
|
+
name: str | None = None,
|
933
|
+
overwrite: bool = True, # << novo parâmetro
|
934
|
+
) -> dict:
|
935
|
+
"""
|
936
|
+
Recebe o conteúdo do PDF em bytes e faz upload para a pasta indicada (criando hierarquia se necessário).
|
937
|
+
"""
|
938
|
+
if not isinstance(content, (bytes, bytearray)) or not content:
|
939
|
+
return {"success": False, "result": None, "error": "Parâmetro 'content' vazio ou inválido."}
|
940
|
+
folder_id = self.ensure_path(drive_folder_path, parent_folder_id=parent_folder_id)
|
941
|
+
|
942
|
+
# Nome do arquivo no Drive
|
943
|
+
final_name = (name or "arquivo.pdf")
|
944
|
+
if not final_name.lower().endswith(".pdf"):
|
945
|
+
final_name += ".pdf"
|
946
|
+
|
947
|
+
fh = BytesIO(content)
|
948
|
+
#return self._gdrive_upload_media(folder_id=folder_id, name=final_name, mimetype="application/pdf", file_handle=fh)
|
949
|
+
return self._gdrive_upload_or_update_media(
|
950
|
+
folder_id=folder_id,
|
951
|
+
name=final_name,
|
952
|
+
mimetype="application/pdf",
|
953
|
+
file_handle=fh,
|
954
|
+
overwrite=overwrite,
|
955
|
+
)
|
956
|
+
|
957
|
+
def upload_pdf_base64_to_drive_path(
|
958
|
+
self,
|
959
|
+
b64_content: str,
|
960
|
+
drive_folder_path: str,
|
961
|
+
parent_folder_id: str = "root",
|
962
|
+
name: str | None = None,
|
963
|
+
strict: bool = True,
|
964
|
+
overwrite: bool = True, # << novo parâmetro
|
965
|
+
) -> dict:
|
966
|
+
"""
|
967
|
+
Recebe o conteúdo do PDF como string Base64 e faz upload para a pasta indicada (criando hierarquia se necessário).
|
968
|
+
- strict=True usa validação estrita no b64 (recomendado).
|
969
|
+
- aceita strings com ou sem prefixo data URL (ex.: 'data:application/pdf;base64,...').
|
970
|
+
"""
|
971
|
+
if not isinstance(b64_content, str) or not b64_content.strip():
|
972
|
+
return {"success": False, "result": None, "error": "Parâmetro 'b64_content' vazio ou inválido."}
|
973
|
+
|
974
|
+
# Remove prefixo data URL se existir
|
975
|
+
# ex.: data:application/pdf;base64,JVBERi0xLjQKJ...
|
976
|
+
if ";base64," in b64_content:
|
977
|
+
b64_content = b64_content.split(";base64,", 1)[1]
|
978
|
+
|
979
|
+
try:
|
980
|
+
content = base64.b64decode(b64_content, validate=strict)
|
981
|
+
except Exception as e:
|
982
|
+
return {"success": False, "result": None, "error": f"Base64 inválido: {e}"}
|
983
|
+
|
984
|
+
return self.upload_pdf_bytes_to_drive_path(
|
985
|
+
content=content,
|
986
|
+
drive_folder_path=drive_folder_path,
|
987
|
+
parent_folder_id=parent_folder_id,
|
988
|
+
name=name,
|
989
|
+
overwrite=overwrite,
|
990
|
+
)
|
@@ -5,7 +5,7 @@ csc_cia_stne/email.py,sha256=y4xyPAe6_Mga5Wf6qAsDzYgn0f-zf2KshfItlWe58z8,8481
|
|
5
5
|
csc_cia_stne/ftp.py,sha256=M9WCaq2hm56jGyszNaPinliFaZS0BNrT7VrVPMjkMg4,10988
|
6
6
|
csc_cia_stne/gcp_bigquery.py,sha256=foq8azvvv_f7uikMDslX9RcUIrx7RAS-Sn0AGW0QFQc,7231
|
7
7
|
csc_cia_stne/gcp_bucket.py,sha256=vMALWiW7IoBCuJAR8bUCpOV6BuBzI9AhRRk3b72OdMk,11515
|
8
|
-
csc_cia_stne/google_drive.py,sha256=
|
8
|
+
csc_cia_stne/google_drive.py,sha256=sfq2arBYrv8OLIfRJTnj067EPbem2gofwIfpbqJ475o,44500
|
9
9
|
csc_cia_stne/karavela.py,sha256=jJCYX43D49gGuzmwwK6bN9XVnv2dXdp9iHnnV5H1LMQ,4794
|
10
10
|
csc_cia_stne/logger_json.py,sha256=CXxSCOFGMymDi8XE9SKnPKjW4D0wJLqDLnxqePS26i8,3187
|
11
11
|
csc_cia_stne/logger_rich.py,sha256=fklgkBb4rblKQd7YZ3q-eWfhGg9eflO2k2-z4pGh_yo,5201
|
@@ -38,8 +38,8 @@ csc_cia_stne/utilitarios/web_screen/__init__.py,sha256=5QcOPXKd95SvP2DoZiHS0gaU6
|
|
38
38
|
csc_cia_stne/utilitarios/web_screen/web_screen_abstract.py,sha256=PjL8Vgfj_JdKidia7RFyCkro3avYLQu4RZRos41sh3w,3241
|
39
39
|
csc_cia_stne/utilitarios/web_screen/web_screen_botcity.py,sha256=Xi5YJjl2pcxlX3OimqcBWRNXZEpAE7asyUjDJ4Oho5U,12297
|
40
40
|
csc_cia_stne/utilitarios/web_screen/web_screen_selenium.py,sha256=JLIcPJE9ZX3Pd6zG6oTRMqqUAY063UzLY3ReRlxmiSM,15581
|
41
|
-
csc_cia_stne-0.1.
|
42
|
-
csc_cia_stne-0.1.
|
43
|
-
csc_cia_stne-0.1.
|
44
|
-
csc_cia_stne-0.1.
|
45
|
-
csc_cia_stne-0.1.
|
41
|
+
csc_cia_stne-0.1.17.dist-info/licenses/LICENCE,sha256=LPGMtgKki2C3KEZP7hDhA1HBrlq5JCHkIeStUCLEMx4,1073
|
42
|
+
csc_cia_stne-0.1.17.dist-info/METADATA,sha256=sJlZR_-JjU01PX7qUYHYGYRFf88JRd0S0mScBwG64vQ,1464
|
43
|
+
csc_cia_stne-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
44
|
+
csc_cia_stne-0.1.17.dist-info/top_level.txt,sha256=ldo7GVv3tQx5KJvwBzdZzzQmjPys2NDVVn1rv0BOF2Q,13
|
45
|
+
csc_cia_stne-0.1.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|