nia-etl-utils 0.1.0__py3-none-any.whl → 0.2.0__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.
- nia_etl_utils/__init__.py +154 -44
- nia_etl_utils/config.py +391 -0
- nia_etl_utils/database.py +249 -153
- nia_etl_utils/email_smtp.py +201 -67
- nia_etl_utils/env_config.py +137 -15
- nia_etl_utils/exceptions.py +327 -0
- nia_etl_utils/limpeza_pastas.py +192 -59
- nia_etl_utils/logger_config.py +98 -40
- nia_etl_utils/processa_csv.py +257 -114
- nia_etl_utils/processa_csv_paralelo.py +150 -37
- nia_etl_utils/results.py +304 -0
- {nia_etl_utils-0.1.0.dist-info → nia_etl_utils-0.2.0.dist-info}/METADATA +22 -1
- nia_etl_utils-0.2.0.dist-info/RECORD +15 -0
- nia_etl_utils-0.1.0.dist-info/RECORD +0 -12
- {nia_etl_utils-0.1.0.dist-info → nia_etl_utils-0.2.0.dist-info}/WHEEL +0 -0
- {nia_etl_utils-0.1.0.dist-info → nia_etl_utils-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Exceções customizadas para o pacote nia_etl_utils.
|
|
2
|
+
|
|
3
|
+
Este módulo define a hierarquia de exceções usada em todo o pacote,
|
|
4
|
+
permitindo tratamento granular de erros pelos consumidores da biblioteca.
|
|
5
|
+
|
|
6
|
+
Hierarquia:
|
|
7
|
+
NiaEtlError (base)
|
|
8
|
+
├── ConfiguracaoError
|
|
9
|
+
│ └── VariavelAmbienteError
|
|
10
|
+
├── DatabaseError
|
|
11
|
+
│ └── ConexaoError
|
|
12
|
+
├── ArquivoError
|
|
13
|
+
│ ├── EscritaArquivoError
|
|
14
|
+
│ ├── LeituraArquivoError
|
|
15
|
+
│ └── DiretorioError
|
|
16
|
+
├── ExtracaoError
|
|
17
|
+
│ └── ExtracaoVaziaError
|
|
18
|
+
├── EmailError
|
|
19
|
+
│ ├── DestinatarioError
|
|
20
|
+
│ └── SmtpError
|
|
21
|
+
└── ValidacaoError
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
Capturando erros específicos:
|
|
25
|
+
|
|
26
|
+
>>> from nia_etl_utils.exceptions import ConexaoError, ExtracaoVaziaError
|
|
27
|
+
>>>
|
|
28
|
+
>>> try:
|
|
29
|
+
... conn = conectar_postgresql(config)
|
|
30
|
+
... except ConexaoError as e:
|
|
31
|
+
... logger.error(f"Falha na conexão: {e}")
|
|
32
|
+
... # tratamento específico
|
|
33
|
+
|
|
34
|
+
Capturando qualquer erro do pacote:
|
|
35
|
+
|
|
36
|
+
>>> from nia_etl_utils.exceptions import NiaEtlError
|
|
37
|
+
>>>
|
|
38
|
+
>>> try:
|
|
39
|
+
... executar_pipeline()
|
|
40
|
+
... except NiaEtlError as e:
|
|
41
|
+
... logger.error(f"Erro no pipeline: {e}")
|
|
42
|
+
... sys.exit(1) # decisão do CHAMADOR
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class NiaEtlError(Exception):
|
|
47
|
+
"""Exceção base para todos os erros do pacote nia_etl_utils.
|
|
48
|
+
|
|
49
|
+
Todas as exceções customizadas do pacote herdam desta classe,
|
|
50
|
+
permitindo captura genérica quando necessário.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
message: Descrição do erro.
|
|
54
|
+
details: Informações adicionais opcionais sobre o erro.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
>>> try:
|
|
58
|
+
... raise NiaEtlError("Algo deu errado", details={"codigo": 123})
|
|
59
|
+
... except NiaEtlError as e:
|
|
60
|
+
... print(e.message)
|
|
61
|
+
... print(e.details)
|
|
62
|
+
Algo deu errado
|
|
63
|
+
{'codigo': 123}
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, message: str, details: dict | None = None):
|
|
67
|
+
self.message = message
|
|
68
|
+
self.details = details or {}
|
|
69
|
+
super().__init__(self.message)
|
|
70
|
+
|
|
71
|
+
def __str__(self) -> str:
|
|
72
|
+
if self.details:
|
|
73
|
+
return f"{self.message} | Detalhes: {self.details}"
|
|
74
|
+
return self.message
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# =============================================================================
|
|
78
|
+
# CONFIGURAÇÃO
|
|
79
|
+
# =============================================================================
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ConfiguracaoError(NiaEtlError):
|
|
83
|
+
"""Erro de configuração do sistema.
|
|
84
|
+
|
|
85
|
+
Levantado quando há problemas com configurações necessárias
|
|
86
|
+
para o funcionamento do pacote.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
>>> raise ConfiguracaoError("Configuração inválida para conexão")
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class VariavelAmbienteError(ConfiguracaoError):
|
|
96
|
+
"""Variável de ambiente ausente ou inválida.
|
|
97
|
+
|
|
98
|
+
Levantado quando uma variável de ambiente obrigatória não está
|
|
99
|
+
definida e nenhum valor padrão foi fornecido.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
nome_variavel: Nome da variável de ambiente que causou o erro.
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
>>> raise VariavelAmbienteError("DB_HOST")
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, nome_variavel: str):
|
|
109
|
+
self.nome_variavel = nome_variavel
|
|
110
|
+
super().__init__(
|
|
111
|
+
f"Variável de ambiente '{nome_variavel}' não encontrada "
|
|
112
|
+
f"e nenhum valor padrão foi fornecido",
|
|
113
|
+
details={"variavel": nome_variavel}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# =============================================================================
|
|
118
|
+
# DATABASE
|
|
119
|
+
# =============================================================================
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class DatabaseError(NiaEtlError):
|
|
123
|
+
"""Erro base para operações de banco de dados.
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> raise DatabaseError("Falha na operação de banco de dados")
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ConexaoError(DatabaseError):
|
|
133
|
+
"""Falha ao estabelecer conexão com banco de dados.
|
|
134
|
+
|
|
135
|
+
Levantado quando não é possível conectar ao banco de dados,
|
|
136
|
+
seja por credenciais inválidas, host inacessível ou outros
|
|
137
|
+
problemas de conectividade.
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
>>> raise ConexaoError(
|
|
141
|
+
... "Timeout ao conectar",
|
|
142
|
+
... details={"host": "localhost", "port": 5432}
|
|
143
|
+
... )
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# =============================================================================
|
|
150
|
+
# ARQUIVOS E DIRETÓRIOS
|
|
151
|
+
# =============================================================================
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ArquivoError(NiaEtlError):
|
|
155
|
+
"""Erro base para operações de arquivo e diretório.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
>>> raise ArquivoError("Operação de arquivo falhou")
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class EscritaArquivoError(ArquivoError):
|
|
165
|
+
"""Falha ao escrever arquivo.
|
|
166
|
+
|
|
167
|
+
Levantado quando não é possível criar ou escrever em um arquivo,
|
|
168
|
+
seja por falta de permissão, disco cheio ou caminho inválido.
|
|
169
|
+
|
|
170
|
+
Examples:
|
|
171
|
+
>>> raise EscritaArquivoError(
|
|
172
|
+
... "Sem permissão para escrita",
|
|
173
|
+
... details={"caminho": "/etc/arquivo.csv"}
|
|
174
|
+
... )
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class LeituraArquivoError(ArquivoError):
|
|
181
|
+
"""Falha ao ler arquivo.
|
|
182
|
+
|
|
183
|
+
Levantado quando não é possível ler um arquivo, seja porque
|
|
184
|
+
ele não existe, não há permissão ou está corrompido.
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
>>> raise LeituraArquivoError(
|
|
188
|
+
... "Arquivo não encontrado",
|
|
189
|
+
... details={"caminho": "/tmp/dados.csv"}
|
|
190
|
+
... )
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class DiretorioError(ArquivoError):
|
|
197
|
+
"""Falha em operação de diretório.
|
|
198
|
+
|
|
199
|
+
Levantado quando não é possível criar, limpar ou remover
|
|
200
|
+
um diretório.
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
>>> raise DiretorioError(
|
|
204
|
+
... "Sem permissão para criar diretório",
|
|
205
|
+
... details={"caminho": "/root/dados"}
|
|
206
|
+
... )
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# =============================================================================
|
|
213
|
+
# EXTRAÇÃO E PROCESSAMENTO
|
|
214
|
+
# =============================================================================
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ExtracaoError(NiaEtlError):
|
|
218
|
+
"""Erro base para operações de extração de dados.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
>>> raise ExtracaoError("Falha na extração de dados")
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ExtracaoVaziaError(ExtracaoError):
|
|
228
|
+
"""Extração retornou DataFrame vazio ou None.
|
|
229
|
+
|
|
230
|
+
Levantado quando uma função de extração não retorna dados.
|
|
231
|
+
Pode ser esperado em alguns contextos (extração incremental
|
|
232
|
+
sem novos dados) ou indicar um problema.
|
|
233
|
+
|
|
234
|
+
Attributes:
|
|
235
|
+
nome_extracao: Identificador da extração que falhou.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
>>> raise ExtracaoVaziaError("clientes_novos")
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, nome_extracao: str):
|
|
242
|
+
self.nome_extracao = nome_extracao
|
|
243
|
+
super().__init__(
|
|
244
|
+
f"Nenhum dado retornado para extração '{nome_extracao}'",
|
|
245
|
+
details={"extracao": nome_extracao}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class ProcessamentoError(ExtracaoError):
|
|
250
|
+
"""Erro durante processamento de dados.
|
|
251
|
+
|
|
252
|
+
Levantado quando há falha durante transformação ou
|
|
253
|
+
processamento de dados.
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
>>> raise ProcessamentoError(
|
|
257
|
+
... "Falha ao processar chunk",
|
|
258
|
+
... details={"chunk": 5, "erro": "memória insuficiente"}
|
|
259
|
+
... )
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# =============================================================================
|
|
266
|
+
# EMAIL
|
|
267
|
+
# =============================================================================
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class EmailError(NiaEtlError):
|
|
271
|
+
"""Erro base para operações de email.
|
|
272
|
+
|
|
273
|
+
Examples:
|
|
274
|
+
>>> raise EmailError("Falha no envio de email")
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class DestinatarioError(EmailError):
|
|
281
|
+
"""Erro relacionado a destinatários de email.
|
|
282
|
+
|
|
283
|
+
Levantado quando não há destinatários configurados ou
|
|
284
|
+
quando os destinatários são inválidos.
|
|
285
|
+
|
|
286
|
+
Examples:
|
|
287
|
+
>>> raise DestinatarioError("Nenhum destinatário configurado")
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class SmtpError(EmailError):
|
|
294
|
+
"""Erro de comunicação com servidor SMTP.
|
|
295
|
+
|
|
296
|
+
Levantado quando há falha na conexão ou comunicação
|
|
297
|
+
com o servidor de email.
|
|
298
|
+
|
|
299
|
+
Examples:
|
|
300
|
+
>>> raise SmtpError(
|
|
301
|
+
... "Conexão recusada",
|
|
302
|
+
... details={"servidor": "smtp.empresa.com", "porta": 587}
|
|
303
|
+
... )
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# =============================================================================
|
|
310
|
+
# VALIDAÇÃO
|
|
311
|
+
# =============================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class ValidacaoError(NiaEtlError):
|
|
315
|
+
"""Erro de validação de parâmetros ou dados.
|
|
316
|
+
|
|
317
|
+
Levantado quando parâmetros fornecidos são inválidos
|
|
318
|
+
ou não atendem aos requisitos esperados.
|
|
319
|
+
|
|
320
|
+
Examples:
|
|
321
|
+
>>> raise ValidacaoError(
|
|
322
|
+
... "Nome do arquivo não pode ser vazio",
|
|
323
|
+
... details={"parametro": "nome_arquivo", "valor": ""}
|
|
324
|
+
... )
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
pass
|
nia_etl_utils/limpeza_pastas.py
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
"""Funções utilitárias para manipulação de arquivos e diretórios.
|
|
2
|
-
|
|
1
|
+
"""Funções utilitárias para manipulação de arquivos e diretórios.
|
|
2
|
+
|
|
3
|
+
Fornece operações comuns de sistema de arquivos com logging
|
|
4
|
+
apropriado e tratamento de erros consistente.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
Limpar pasta antes de processamento:
|
|
8
|
+
|
|
9
|
+
>>> from nia_etl_utils import limpar_pasta
|
|
10
|
+
>>> limpar_pasta("/tmp/meu_pipeline")
|
|
11
|
+
|
|
12
|
+
Criar estrutura de diretórios:
|
|
13
|
+
|
|
14
|
+
>>> from nia_etl_utils import criar_pasta_se_nao_existir
|
|
15
|
+
>>> criar_pasta_se_nao_existir("/dados/processados/2025/01")
|
|
16
|
+
|
|
17
|
+
Remover pasta temporária:
|
|
18
|
+
|
|
19
|
+
>>> from nia_etl_utils import remover_pasta_recursivamente
|
|
20
|
+
>>> remover_pasta_recursivamente("/tmp/pasta_temporaria")
|
|
21
|
+
"""
|
|
22
|
+
|
|
3
23
|
import shutil
|
|
4
24
|
from pathlib import Path
|
|
25
|
+
|
|
5
26
|
from loguru import logger
|
|
6
27
|
|
|
28
|
+
from .exceptions import DiretorioError
|
|
29
|
+
|
|
7
30
|
|
|
8
|
-
def limpar_pasta(pasta: str, log: bool = True) ->
|
|
9
|
-
"""Remove todos os arquivos de uma pasta,
|
|
31
|
+
def limpar_pasta(pasta: str, log: bool = True) -> int:
|
|
32
|
+
"""Remove todos os arquivos de uma pasta, preservando subdiretórios.
|
|
10
33
|
|
|
11
34
|
Se a pasta não existir, ela será criada. Se existir, todos os arquivos
|
|
12
35
|
dentro dela serão removidos (subdiretórios são preservados).
|
|
@@ -15,13 +38,22 @@ def limpar_pasta(pasta: str, log: bool = True) -> None:
|
|
|
15
38
|
pasta: Caminho da pasta que será limpa.
|
|
16
39
|
log: Se True, emite logs com Loguru. Defaults to True.
|
|
17
40
|
|
|
41
|
+
Returns:
|
|
42
|
+
Número de arquivos removidos.
|
|
43
|
+
|
|
18
44
|
Raises:
|
|
19
|
-
|
|
45
|
+
DiretorioError: Se houver erro ao criar ou limpar a pasta.
|
|
20
46
|
|
|
21
47
|
Examples:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
>>>
|
|
48
|
+
Limpar pasta de saída antes de processamento:
|
|
49
|
+
|
|
50
|
+
>>> from nia_etl_utils import limpar_pasta
|
|
51
|
+
>>> removidos = limpar_pasta("/tmp/meu_pipeline")
|
|
52
|
+
>>> print(f"{removidos} arquivo(s) removido(s)")
|
|
53
|
+
|
|
54
|
+
Limpar sem logging:
|
|
55
|
+
|
|
56
|
+
>>> limpar_pasta("/tmp/dados", log=False)
|
|
25
57
|
"""
|
|
26
58
|
try:
|
|
27
59
|
pasta_path = Path(pasta)
|
|
@@ -30,46 +62,66 @@ def limpar_pasta(pasta: str, log: bool = True) -> None:
|
|
|
30
62
|
pasta_path.mkdir(parents=True, exist_ok=True)
|
|
31
63
|
if log:
|
|
32
64
|
logger.info(f"Pasta criada: {pasta}")
|
|
33
|
-
|
|
34
|
-
arquivos_removidos = 0
|
|
65
|
+
return 0
|
|
35
66
|
|
|
36
|
-
|
|
37
|
-
if item.is_file():
|
|
38
|
-
item.unlink()
|
|
39
|
-
arquivos_removidos += 1
|
|
40
|
-
if log:
|
|
41
|
-
logger.debug(f"Arquivo removido: {item}")
|
|
67
|
+
arquivos_removidos = 0
|
|
42
68
|
|
|
43
|
-
|
|
44
|
-
|
|
69
|
+
for item in pasta_path.iterdir():
|
|
70
|
+
if item.is_file():
|
|
71
|
+
item.unlink()
|
|
72
|
+
arquivos_removidos += 1
|
|
73
|
+
if log:
|
|
74
|
+
logger.debug(f"Arquivo removido: {item}")
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
sys.exit(1)
|
|
52
|
-
except Exception as error:
|
|
53
|
-
logger.error(f"Erro inesperado ao limpar a pasta '{pasta}': {error}")
|
|
54
|
-
sys.exit(1)
|
|
76
|
+
if log:
|
|
77
|
+
logger.info(
|
|
78
|
+
f"Pasta '{pasta}' limpa com sucesso. "
|
|
79
|
+
f"{arquivos_removidos} arquivo(s) removido(s)."
|
|
80
|
+
)
|
|
55
81
|
|
|
82
|
+
return arquivos_removidos
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
except PermissionError as e:
|
|
85
|
+
raise DiretorioError(
|
|
86
|
+
f"Sem permissão para acessar/modificar a pasta '{pasta}'",
|
|
87
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
88
|
+
) from e
|
|
89
|
+
except OSError as e:
|
|
90
|
+
raise DiretorioError(
|
|
91
|
+
f"Erro do sistema ao manipular a pasta '{pasta}'",
|
|
92
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
93
|
+
) from e
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def remover_pasta_recursivamente(pasta: str, log: bool = True) -> bool:
|
|
58
97
|
"""Remove uma pasta e todo seu conteúdo (arquivos e subpastas).
|
|
59
98
|
|
|
60
|
-
ATENÇÃO: Esta função remove TUDO dentro da pasta, incluindo
|
|
61
|
-
Use com cautela.
|
|
99
|
+
ATENÇÃO: Esta função remove TUDO dentro da pasta, incluindo
|
|
100
|
+
subdiretórios. Use com cautela.
|
|
62
101
|
|
|
63
102
|
Args:
|
|
64
103
|
pasta: Caminho da pasta que será removida completamente.
|
|
65
104
|
log: Se True, emite logs com Loguru. Defaults to True.
|
|
66
105
|
|
|
106
|
+
Returns:
|
|
107
|
+
True se a pasta foi removida, False se não existia.
|
|
108
|
+
|
|
67
109
|
Raises:
|
|
68
|
-
|
|
110
|
+
DiretorioError: Se o caminho não for um diretório ou
|
|
111
|
+
houver erro ao remover.
|
|
69
112
|
|
|
70
113
|
Examples:
|
|
71
|
-
|
|
72
|
-
|
|
114
|
+
Remover pasta temporária:
|
|
115
|
+
|
|
116
|
+
>>> from nia_etl_utils import remover_pasta_recursivamente
|
|
117
|
+
>>> if remover_pasta_recursivamente("/tmp/pasta_temporaria"):
|
|
118
|
+
... print("Pasta removida")
|
|
119
|
+
... else:
|
|
120
|
+
... print("Pasta não existia")
|
|
121
|
+
|
|
122
|
+
Remover sem logging:
|
|
123
|
+
|
|
124
|
+
>>> remover_pasta_recursivamente("/tmp/dados", log=False)
|
|
73
125
|
"""
|
|
74
126
|
try:
|
|
75
127
|
pasta_path = Path(pasta)
|
|
@@ -77,41 +129,58 @@ def remover_pasta_recursivamente(pasta: str, log: bool = True) -> None:
|
|
|
77
129
|
if not pasta_path.exists():
|
|
78
130
|
if log:
|
|
79
131
|
logger.warning(f"Pasta '{pasta}' não existe. Nada a remover.")
|
|
80
|
-
return
|
|
132
|
+
return False
|
|
81
133
|
|
|
82
134
|
if not pasta_path.is_dir():
|
|
83
|
-
|
|
84
|
-
|
|
135
|
+
raise DiretorioError(
|
|
136
|
+
f"'{pasta}' não é um diretório",
|
|
137
|
+
details={"pasta": pasta, "tipo": "arquivo"}
|
|
138
|
+
)
|
|
85
139
|
|
|
86
140
|
shutil.rmtree(pasta_path)
|
|
87
141
|
|
|
88
142
|
if log:
|
|
89
143
|
logger.info(f"Pasta '{pasta}' removida completamente (incluindo subpastas).")
|
|
90
144
|
|
|
91
|
-
|
|
92
|
-
logger.error(f"Sem permissão para remover a pasta '{pasta}': {error}")
|
|
93
|
-
sys.exit(1)
|
|
94
|
-
except OSError as error:
|
|
95
|
-
logger.error(f"Erro do sistema ao remover a pasta '{pasta}': {error}")
|
|
96
|
-
sys.exit(1)
|
|
97
|
-
except Exception as error:
|
|
98
|
-
logger.error(f"Erro inesperado ao remover a pasta '{pasta}': {error}")
|
|
99
|
-
sys.exit(1)
|
|
145
|
+
return True
|
|
100
146
|
|
|
147
|
+
except PermissionError as e:
|
|
148
|
+
raise DiretorioError(
|
|
149
|
+
f"Sem permissão para remover a pasta '{pasta}'",
|
|
150
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
151
|
+
) from e
|
|
152
|
+
except OSError as e:
|
|
153
|
+
raise DiretorioError(
|
|
154
|
+
f"Erro do sistema ao remover a pasta '{pasta}'",
|
|
155
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
156
|
+
) from e
|
|
101
157
|
|
|
102
|
-
|
|
158
|
+
|
|
159
|
+
def criar_pasta_se_nao_existir(pasta: str, log: bool = True) -> bool:
|
|
103
160
|
"""Cria uma pasta se ela não existir (incluindo pastas pai).
|
|
104
161
|
|
|
105
162
|
Args:
|
|
106
163
|
pasta: Caminho da pasta que será criada.
|
|
107
164
|
log: Se True, emite logs com Loguru. Defaults to True.
|
|
108
165
|
|
|
166
|
+
Returns:
|
|
167
|
+
True se a pasta foi criada, False se já existia.
|
|
168
|
+
|
|
109
169
|
Raises:
|
|
110
|
-
|
|
170
|
+
DiretorioError: Se houver erro ao criar a pasta.
|
|
111
171
|
|
|
112
172
|
Examples:
|
|
113
|
-
|
|
114
|
-
|
|
173
|
+
Criar estrutura de diretórios:
|
|
174
|
+
|
|
175
|
+
>>> from nia_etl_utils import criar_pasta_se_nao_existir
|
|
176
|
+
>>> if criar_pasta_se_nao_existir("/tmp/dados/processados/2025"):
|
|
177
|
+
... print("Estrutura criada")
|
|
178
|
+
... else:
|
|
179
|
+
... print("Já existia")
|
|
180
|
+
|
|
181
|
+
Criar sem logging:
|
|
182
|
+
|
|
183
|
+
>>> criar_pasta_se_nao_existir("/tmp/dados", log=False)
|
|
115
184
|
"""
|
|
116
185
|
try:
|
|
117
186
|
pasta_path = Path(pasta)
|
|
@@ -119,19 +188,83 @@ def criar_pasta_se_nao_existir(pasta: str, log: bool = True) -> None:
|
|
|
119
188
|
if pasta_path.exists():
|
|
120
189
|
if log:
|
|
121
190
|
logger.debug(f"Pasta '{pasta}' já existe.")
|
|
122
|
-
return
|
|
191
|
+
return False
|
|
123
192
|
|
|
124
193
|
pasta_path.mkdir(parents=True, exist_ok=True)
|
|
125
194
|
|
|
126
195
|
if log:
|
|
127
196
|
logger.info(f"Pasta criada: {pasta}")
|
|
128
197
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
except PermissionError as e:
|
|
201
|
+
raise DiretorioError(
|
|
202
|
+
f"Sem permissão para criar a pasta '{pasta}'",
|
|
203
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
204
|
+
) from e
|
|
205
|
+
except OSError as e:
|
|
206
|
+
raise DiretorioError(
|
|
207
|
+
f"Erro do sistema ao criar a pasta '{pasta}'",
|
|
208
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
209
|
+
) from e
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def listar_arquivos(
|
|
213
|
+
pasta: str,
|
|
214
|
+
extensao: str | None = None,
|
|
215
|
+
recursivo: bool = False
|
|
216
|
+
) -> list[Path]:
|
|
217
|
+
"""Lista arquivos em uma pasta.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
pasta: Caminho da pasta a ser listada.
|
|
221
|
+
extensao: Filtrar por extensão (ex: ".csv", ".json").
|
|
222
|
+
Se None, lista todos os arquivos.
|
|
223
|
+
recursivo: Se True, inclui arquivos em subpastas.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Lista de objetos Path para cada arquivo encontrado.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
DiretorioError: Se a pasta não existir ou não for acessível.
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
Listar todos os CSVs:
|
|
233
|
+
|
|
234
|
+
>>> arquivos = listar_arquivos("/tmp/dados", extensao=".csv")
|
|
235
|
+
>>> for arq in arquivos:
|
|
236
|
+
... print(arq.name)
|
|
237
|
+
|
|
238
|
+
Listar recursivamente:
|
|
239
|
+
|
|
240
|
+
>>> arquivos = listar_arquivos("/tmp/dados", recursivo=True)
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
pasta_path = Path(pasta)
|
|
244
|
+
|
|
245
|
+
if not pasta_path.exists():
|
|
246
|
+
raise DiretorioError(
|
|
247
|
+
f"Pasta '{pasta}' não existe",
|
|
248
|
+
details={"pasta": pasta}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not pasta_path.is_dir():
|
|
252
|
+
raise DiretorioError(
|
|
253
|
+
f"'{pasta}' não é um diretório",
|
|
254
|
+
details={"pasta": pasta}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if recursivo:
|
|
258
|
+
pattern = "**/*" if extensao is None else f"**/*{extensao}"
|
|
259
|
+
arquivos = [p for p in pasta_path.glob(pattern) if p.is_file()]
|
|
260
|
+
else:
|
|
261
|
+
pattern = "*" if extensao is None else f"*{extensao}"
|
|
262
|
+
arquivos = [p for p in pasta_path.glob(pattern) if p.is_file()]
|
|
263
|
+
|
|
264
|
+
return sorted(arquivos)
|
|
265
|
+
|
|
266
|
+
except PermissionError as e:
|
|
267
|
+
raise DiretorioError(
|
|
268
|
+
f"Sem permissão para acessar a pasta '{pasta}'",
|
|
269
|
+
details={"pasta": pasta, "erro": str(e)}
|
|
270
|
+
) from e
|