nia-etl-utils 0.1.0__py3-none-any.whl → 0.2.1__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 +173 -43
- 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 +394 -0
- nia_etl_utils/limpeza_pastas.py +192 -59
- nia_etl_utils/logger_config.py +98 -40
- nia_etl_utils/ocr.py +401 -0
- 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.2.1.dist-info/METADATA +723 -0
- nia_etl_utils-0.2.1.dist-info/RECORD +16 -0
- {nia_etl_utils-0.1.0.dist-info → nia_etl_utils-0.2.1.dist-info}/WHEEL +1 -1
- nia_etl_utils-0.1.0.dist-info/METADATA +0 -594
- nia_etl_utils-0.1.0.dist-info/RECORD +0 -12
- {nia_etl_utils-0.1.0.dist-info → nia_etl_utils-0.2.1.dist-info}/top_level.txt +0 -0
nia_etl_utils/processa_csv.py
CHANGED
|
@@ -1,14 +1,51 @@
|
|
|
1
1
|
"""Módulo para exportação de DataFrame para CSV.
|
|
2
2
|
|
|
3
3
|
Fornece funções utilitárias para salvar DataFrames em formato CSV com
|
|
4
|
-
nomenclatura padronizada e
|
|
4
|
+
nomenclatura padronizada, logging adequado e tratamento de erros.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
Exportação simples:
|
|
8
|
+
|
|
9
|
+
>>> from nia_etl_utils import exportar_para_csv
|
|
10
|
+
>>> import pandas as pd
|
|
11
|
+
>>> df = pd.DataFrame({"id": [1, 2], "valor": [100, 200]})
|
|
12
|
+
>>> caminho = exportar_para_csv(df, "vendas", "2025_01_20", "/tmp/dados")
|
|
13
|
+
|
|
14
|
+
Extração e exportação:
|
|
15
|
+
|
|
16
|
+
>>> from nia_etl_utils import extrair_e_exportar_csv
|
|
17
|
+
>>> def extrair_clientes():
|
|
18
|
+
... return pd.DataFrame({"id": [1, 2], "nome": ["Ana", "João"]})
|
|
19
|
+
>>> resultado = extrair_e_exportar_csv(
|
|
20
|
+
... nome_extracao="clientes",
|
|
21
|
+
... funcao_extracao=extrair_clientes,
|
|
22
|
+
... data_extracao="2025_01_20",
|
|
23
|
+
... diretorio_base="/tmp/dados"
|
|
24
|
+
... )
|
|
25
|
+
|
|
26
|
+
Múltiplas extrações:
|
|
27
|
+
|
|
28
|
+
>>> extractions = [
|
|
29
|
+
... {"nome": "clientes", "funcao": extrair_clientes},
|
|
30
|
+
... {"nome": "vendas", "funcao": extrair_vendas},
|
|
31
|
+
... ]
|
|
32
|
+
>>> resultados = exportar_multiplos_csv(extractions, "2025_01_20", "/tmp")
|
|
5
33
|
"""
|
|
6
|
-
|
|
34
|
+
|
|
35
|
+
from collections.abc import Callable
|
|
7
36
|
from pathlib import Path
|
|
8
|
-
|
|
37
|
+
|
|
9
38
|
import pandas as pd
|
|
10
39
|
from loguru import logger
|
|
11
40
|
|
|
41
|
+
from .exceptions import (
|
|
42
|
+
EscritaArquivoError,
|
|
43
|
+
ExtracaoError,
|
|
44
|
+
ExtracaoVaziaError,
|
|
45
|
+
ValidacaoError,
|
|
46
|
+
)
|
|
47
|
+
from .results import ResultadoExtracao, ResultadoLote
|
|
48
|
+
|
|
12
49
|
|
|
13
50
|
def exportar_para_csv(
|
|
14
51
|
df: pd.DataFrame,
|
|
@@ -18,36 +55,59 @@ def exportar_para_csv(
|
|
|
18
55
|
) -> str:
|
|
19
56
|
"""Salva um DataFrame como arquivo CSV.
|
|
20
57
|
|
|
58
|
+
Cria o diretório de destino se não existir e salva o DataFrame
|
|
59
|
+
com nomenclatura padronizada: {nome_arquivo}_{data_extracao}.csv
|
|
60
|
+
|
|
21
61
|
Args:
|
|
22
|
-
df: DataFrame a ser salvo.
|
|
23
|
-
nome_arquivo: Nome base do arquivo (sem extensão).
|
|
24
|
-
data_extracao: Data
|
|
25
|
-
|
|
62
|
+
df: DataFrame a ser salvo. Não pode ser None ou vazio.
|
|
63
|
+
nome_arquivo: Nome base do arquivo (sem extensão e sem data).
|
|
64
|
+
data_extracao: Data no formato string para compor o nome
|
|
65
|
+
do arquivo (ex: "2025_01_19").
|
|
66
|
+
diretorio_base: Caminho do diretório onde o arquivo será salvo.
|
|
67
|
+
Será criado se não existir.
|
|
26
68
|
|
|
27
69
|
Returns:
|
|
28
|
-
|
|
70
|
+
Caminho absoluto do arquivo CSV salvo.
|
|
29
71
|
|
|
30
72
|
Raises:
|
|
31
|
-
|
|
73
|
+
ExtracaoVaziaError: Se df for None ou vazio.
|
|
74
|
+
ValidacaoError: Se nome_arquivo for vazio ou apenas espaços.
|
|
75
|
+
EscritaArquivoError: Se houver erro de permissão ou sistema
|
|
76
|
+
ao salvar o arquivo.
|
|
32
77
|
|
|
33
78
|
Examples:
|
|
79
|
+
Exportação básica:
|
|
80
|
+
|
|
34
81
|
>>> import pandas as pd
|
|
35
|
-
>>>
|
|
36
|
-
>>>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
82
|
+
>>> df = pd.DataFrame({"id": [1, 2], "valor": [100, 200]})
|
|
83
|
+
>>> caminho = exportar_para_csv(
|
|
84
|
+
... df=df,
|
|
85
|
+
... nome_arquivo="vendas",
|
|
86
|
+
... data_extracao="2025_01_19",
|
|
87
|
+
... diretorio_base="/tmp/dados"
|
|
88
|
+
... )
|
|
89
|
+
>>> print(caminho)
|
|
90
|
+
/tmp/dados/vendas_2025_01_19.csv
|
|
91
|
+
|
|
92
|
+
Com tratamento de erro:
|
|
46
93
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
94
|
+
>>> from nia_etl_utils.exceptions import ExtracaoVaziaError
|
|
95
|
+
>>> try:
|
|
96
|
+
... exportar_para_csv(pd.DataFrame(), "vazio", "2025_01_19", "/tmp")
|
|
97
|
+
... except ExtracaoVaziaError:
|
|
98
|
+
... print("DataFrame estava vazio")
|
|
99
|
+
"""
|
|
100
|
+
# Validações
|
|
101
|
+
if df is None or df.empty:
|
|
102
|
+
raise ExtracaoVaziaError(nome_arquivo)
|
|
103
|
+
|
|
104
|
+
if not nome_arquivo or not nome_arquivo.strip():
|
|
105
|
+
raise ValidacaoError(
|
|
106
|
+
"Nome do arquivo não pode ser vazio",
|
|
107
|
+
details={"parametro": "nome_arquivo", "valor": nome_arquivo}
|
|
108
|
+
)
|
|
50
109
|
|
|
110
|
+
try:
|
|
51
111
|
# Cria diretório se não existir
|
|
52
112
|
diretorio = Path(diretorio_base)
|
|
53
113
|
diretorio.mkdir(parents=True, exist_ok=True)
|
|
@@ -67,15 +127,16 @@ def exportar_para_csv(
|
|
|
67
127
|
|
|
68
128
|
return str(caminho_arquivo)
|
|
69
129
|
|
|
70
|
-
except PermissionError as
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
except PermissionError as e:
|
|
131
|
+
raise EscritaArquivoError(
|
|
132
|
+
f"Sem permissão para salvar arquivo em '{diretorio_base}'",
|
|
133
|
+
details={"diretorio": diretorio_base, "erro": str(e)}
|
|
134
|
+
) from e
|
|
135
|
+
except OSError as e:
|
|
136
|
+
raise EscritaArquivoError(
|
|
137
|
+
f"Erro do sistema ao salvar CSV em '{diretorio_base}'",
|
|
138
|
+
details={"diretorio": diretorio_base, "erro": str(e)}
|
|
139
|
+
) from e
|
|
79
140
|
|
|
80
141
|
|
|
81
142
|
def extrair_e_exportar_csv(
|
|
@@ -83,127 +144,209 @@ def extrair_e_exportar_csv(
|
|
|
83
144
|
funcao_extracao: Callable[[], pd.DataFrame],
|
|
84
145
|
data_extracao: str,
|
|
85
146
|
diretorio_base: str,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
147
|
+
) -> ResultadoExtracao:
|
|
148
|
+
"""Executa função de extração e salva resultado como CSV.
|
|
149
|
+
|
|
150
|
+
Orquestra o fluxo completo: executa a função fornecida, valida
|
|
151
|
+
o DataFrame retornado e persiste como CSV no diretório especificado.
|
|
89
152
|
|
|
90
153
|
Args:
|
|
91
|
-
nome_extracao:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
154
|
+
nome_extracao: Identificador da extração. Usado no nome do arquivo
|
|
155
|
+
e nos logs.
|
|
156
|
+
funcao_extracao: Callable sem argumentos que retorna pd.DataFrame.
|
|
157
|
+
Será executada dentro de try/except para captura de erros.
|
|
158
|
+
data_extracao: Data no formato string para compor o nome do arquivo
|
|
159
|
+
(ex: "2025_01_19").
|
|
160
|
+
diretorio_base: Caminho do diretório onde o arquivo será salvo.
|
|
97
161
|
|
|
98
162
|
Returns:
|
|
99
|
-
|
|
163
|
+
ResultadoExtracao contendo:
|
|
164
|
+
- nome: Identificador da extração
|
|
165
|
+
- caminho: Path do arquivo salvo
|
|
166
|
+
- linhas: Quantidade de registros extraídos
|
|
167
|
+
- sucesso: True se exportação completou
|
|
168
|
+
- erro: None se sucesso, mensagem se falha
|
|
169
|
+
- colunas: Quantidade de colunas
|
|
170
|
+
- tamanho_bytes: Tamanho do arquivo
|
|
100
171
|
|
|
101
172
|
Raises:
|
|
102
|
-
|
|
103
|
-
|
|
173
|
+
ExtracaoVaziaError: Se a função retornar DataFrame vazio ou None.
|
|
174
|
+
ExtracaoError: Se houver erro na execução da função de extração.
|
|
175
|
+
ValidacaoError: Se parâmetros de exportação forem inválidos.
|
|
176
|
+
EscritaArquivoError: Se houver erro ao persistir o arquivo.
|
|
104
177
|
|
|
105
178
|
Examples:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
>>> def
|
|
109
|
-
... return pd.DataFrame({"
|
|
110
|
-
|
|
111
|
-
>>>
|
|
112
|
-
... nome_extracao="
|
|
113
|
-
... funcao_extracao=
|
|
179
|
+
Extração simples:
|
|
180
|
+
|
|
181
|
+
>>> def extrair_clientes():
|
|
182
|
+
... return pd.DataFrame({"id": [1, 2], "nome": ["Ana", "João"]})
|
|
183
|
+
...
|
|
184
|
+
>>> resultado = extrair_e_exportar_csv(
|
|
185
|
+
... nome_extracao="clientes",
|
|
186
|
+
... funcao_extracao=extrair_clientes,
|
|
114
187
|
... data_extracao="2025_01_19",
|
|
115
188
|
... diretorio_base="/tmp/dados"
|
|
116
189
|
... )
|
|
190
|
+
>>> resultado.sucesso
|
|
191
|
+
True
|
|
192
|
+
>>> resultado.linhas
|
|
193
|
+
2
|
|
194
|
+
|
|
195
|
+
Tratando extração vazia:
|
|
196
|
+
|
|
197
|
+
>>> from nia_etl_utils.exceptions import ExtracaoVaziaError
|
|
198
|
+
>>> def extrair_vazia():
|
|
199
|
+
... return pd.DataFrame()
|
|
200
|
+
...
|
|
201
|
+
>>> try:
|
|
202
|
+
... extrair_e_exportar_csv(
|
|
203
|
+
... nome_extracao="vazia",
|
|
204
|
+
... funcao_extracao=extrair_vazia,
|
|
205
|
+
... data_extracao="2025_01_19",
|
|
206
|
+
... diretorio_base="/tmp/dados"
|
|
207
|
+
... )
|
|
208
|
+
... except ExtracaoVaziaError as e:
|
|
209
|
+
... print(f"Esperado: {e.nome_extracao}")
|
|
210
|
+
Esperado: vazia
|
|
117
211
|
"""
|
|
118
|
-
|
|
119
|
-
logger.info(f"Iniciando extração: {nome_extracao}")
|
|
212
|
+
logger.info(f"Iniciando extração: {nome_extracao}")
|
|
120
213
|
|
|
121
|
-
|
|
214
|
+
# Executa função de extração
|
|
215
|
+
try:
|
|
122
216
|
df_extraido = funcao_extracao()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
217
|
+
except Exception as e:
|
|
218
|
+
raise ExtracaoError(
|
|
219
|
+
f"Erro ao executar extração '{nome_extracao}'",
|
|
220
|
+
details={"extracao": nome_extracao, "erro": str(e)}
|
|
221
|
+
) from e
|
|
222
|
+
|
|
223
|
+
# Valida resultado
|
|
224
|
+
if df_extraido is None or df_extraido.empty:
|
|
225
|
+
raise ExtracaoVaziaError(nome_extracao)
|
|
226
|
+
|
|
227
|
+
# Exporta para CSV
|
|
228
|
+
caminho = exportar_para_csv(
|
|
229
|
+
df=df_extraido,
|
|
230
|
+
nome_arquivo=nome_extracao,
|
|
231
|
+
data_extracao=data_extracao,
|
|
232
|
+
diretorio_base=diretorio_base,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Coleta métricas
|
|
236
|
+
tamanho_bytes = Path(caminho).stat().st_size
|
|
237
|
+
|
|
238
|
+
logger.success(f"Extração concluída: {nome_extracao}")
|
|
239
|
+
|
|
240
|
+
return ResultadoExtracao(
|
|
241
|
+
nome=nome_extracao,
|
|
242
|
+
caminho=caminho,
|
|
243
|
+
linhas=len(df_extraido),
|
|
244
|
+
sucesso=True,
|
|
245
|
+
colunas=len(df_extraido.columns),
|
|
246
|
+
tamanho_bytes=tamanho_bytes
|
|
247
|
+
)
|
|
149
248
|
|
|
150
249
|
|
|
151
250
|
def exportar_multiplos_csv(
|
|
152
251
|
extractions: list[dict],
|
|
153
252
|
data_extracao: str,
|
|
154
253
|
diretorio_base: str,
|
|
155
|
-
|
|
156
|
-
) ->
|
|
157
|
-
"""Executa múltiplas extrações e salva cada uma como CSV.
|
|
254
|
+
ignorar_vazios: bool = True
|
|
255
|
+
) -> ResultadoLote:
|
|
256
|
+
"""Executa múltiplas extrações em lote e salva cada uma como CSV.
|
|
257
|
+
|
|
258
|
+
Itera sobre a lista de extrações, executando cada uma sequencialmente.
|
|
259
|
+
O comportamento ao encontrar extrações vazias é controlado pelo
|
|
260
|
+
parâmetro ignorar_vazios.
|
|
158
261
|
|
|
159
262
|
Args:
|
|
160
|
-
extractions: Lista de dicionários
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
263
|
+
extractions: Lista de dicionários, cada um contendo:
|
|
264
|
+
- nome (str): Identificador da extração
|
|
265
|
+
- funcao (Callable[[], pd.DataFrame]): Função de extração
|
|
266
|
+
data_extracao: Data no formato string para compor os nomes dos
|
|
267
|
+
arquivos (ex: "2025_01_19").
|
|
268
|
+
diretorio_base: Caminho do diretório onde os arquivos serão salvos.
|
|
269
|
+
ignorar_vazios: Comportamento quando uma extração retorna vazio.
|
|
270
|
+
Se True (default), loga warning e continua com as próximas.
|
|
271
|
+
Se False, levanta ExtracaoVaziaError imediatamente.
|
|
164
272
|
|
|
165
273
|
Returns:
|
|
166
|
-
|
|
274
|
+
ResultadoLote contendo lista de ResultadoExtracao e métricas
|
|
275
|
+
consolidadas (total, sucesso, falhas, taxa de sucesso).
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ExtracaoVaziaError: Se ignorar_vazios=False e alguma extração
|
|
279
|
+
retornar DataFrame vazio ou None.
|
|
280
|
+
ExtracaoError: Se houver erro crítico em alguma extração
|
|
281
|
+
(não relacionado a dados vazios).
|
|
167
282
|
|
|
168
283
|
Examples:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
>>> def extrair_clientes():
|
|
172
|
-
... return pd.DataFrame({"id": [1, 2]})
|
|
173
|
-
>>>
|
|
174
|
-
>>> def extrair_vendas():
|
|
175
|
-
... return pd.DataFrame({"valor": [100, 200]})
|
|
176
|
-
>>>
|
|
284
|
+
Múltiplas extrações tolerando vazios:
|
|
285
|
+
|
|
177
286
|
>>> extractions = [
|
|
178
287
|
... {"nome": "clientes", "funcao": extrair_clientes},
|
|
179
|
-
... {"nome": "vendas", "funcao": extrair_vendas}
|
|
288
|
+
... {"nome": "vendas", "funcao": extrair_vendas},
|
|
180
289
|
... ]
|
|
181
|
-
>>>
|
|
182
|
-
>>> resultados = exportar_multiplos_csv(
|
|
290
|
+
>>> lote = exportar_multiplos_csv(
|
|
183
291
|
... extractions=extractions,
|
|
184
292
|
... data_extracao="2025_01_19",
|
|
185
293
|
... diretorio_base="/tmp/dados"
|
|
186
294
|
... )
|
|
295
|
+
>>> print(f"{lote.sucesso}/{lote.total} bem-sucedidas")
|
|
296
|
+
>>> for r in lote.extracoes_sucesso:
|
|
297
|
+
... print(f"{r.nome}: {r.linhas} linhas")
|
|
298
|
+
|
|
299
|
+
Falhando na primeira extração vazia:
|
|
300
|
+
|
|
301
|
+
>>> try:
|
|
302
|
+
... exportar_multiplos_csv(
|
|
303
|
+
... extractions=extractions,
|
|
304
|
+
... data_extracao="2025_01_19",
|
|
305
|
+
... diretorio_base="/tmp/dados",
|
|
306
|
+
... ignorar_vazios=False
|
|
307
|
+
... )
|
|
308
|
+
... except ExtracaoVaziaError:
|
|
309
|
+
... print("Pipeline interrompido por extração vazia")
|
|
310
|
+
|
|
311
|
+
Verificando falhas:
|
|
312
|
+
|
|
313
|
+
>>> lote = exportar_multiplos_csv(extractions, "2025_01_19", "/tmp")
|
|
314
|
+
>>> if not lote.todos_sucesso:
|
|
315
|
+
... for falha in lote.extracoes_falhas:
|
|
316
|
+
... logger.warning(f"{falha.nome}: {falha.erro}")
|
|
187
317
|
"""
|
|
188
|
-
|
|
189
|
-
|
|
318
|
+
lote = ResultadoLote()
|
|
190
319
|
logger.info(f"Iniciando {len(extractions)} extrações em lote")
|
|
191
320
|
|
|
192
321
|
for extracao in extractions:
|
|
193
322
|
nome = extracao["nome"]
|
|
194
323
|
funcao = extracao["funcao"]
|
|
195
324
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
325
|
+
try:
|
|
326
|
+
resultado = extrair_e_exportar_csv(
|
|
327
|
+
nome_extracao=nome,
|
|
328
|
+
funcao_extracao=funcao,
|
|
329
|
+
data_extracao=data_extracao,
|
|
330
|
+
diretorio_base=diretorio_base,
|
|
331
|
+
)
|
|
332
|
+
lote.adicionar(resultado)
|
|
333
|
+
|
|
334
|
+
except ExtracaoVaziaError as e:
|
|
335
|
+
if ignorar_vazios:
|
|
336
|
+
logger.warning(str(e))
|
|
337
|
+
lote.adicionar(ResultadoExtracao(
|
|
338
|
+
nome=nome,
|
|
339
|
+
caminho=None,
|
|
340
|
+
linhas=0,
|
|
341
|
+
sucesso=False,
|
|
342
|
+
erro=str(e)
|
|
343
|
+
))
|
|
344
|
+
else:
|
|
345
|
+
raise
|
|
205
346
|
|
|
206
|
-
|
|
207
|
-
|
|
347
|
+
logger.info(
|
|
348
|
+
f"Extrações concluídas: {lote.sucesso}/{lote.total} bem-sucedidas "
|
|
349
|
+
f"({lote.taxa_sucesso:.0%})"
|
|
350
|
+
)
|
|
208
351
|
|
|
209
|
-
return
|
|
352
|
+
return lote
|