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.
@@ -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 logging adequado.
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
- import sys
34
+
35
+ from collections.abc import Callable
7
36
  from pathlib import Path
8
- from typing import Callable, Optional
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 que será usada no nome do arquivo (ex: "2025_01_19").
25
- diretorio_base: Diretório onde o arquivo será salvo.
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
- str: Caminho completo do arquivo salvo.
70
+ Caminho absoluto do arquivo CSV salvo.
29
71
 
30
72
  Raises:
31
- SystemExit: Se houver erro ao salvar o arquivo.
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
- >>> from nia_etl_utils.processa_csv import exportar_para_csv
36
- >>>
37
- >>> df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
38
- >>> caminho = exportar_para_csv(df, "dados", "2025_01_19", "/tmp")
39
- >>> # Arquivo salvo: /tmp/dados_2025_01_19.csv
40
- """
41
- try:
42
- # Valida inputs
43
- if df is None or df.empty:
44
- logger.warning("DataFrame vazio ou None fornecido. Nenhum arquivo será criado.")
45
- return ""
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
- if not nome_arquivo or not nome_arquivo.strip():
48
- logger.error("Nome do arquivo não pode ser vazio.")
49
- sys.exit(1)
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 error:
71
- logger.error(f"Sem permissão para salvar arquivo em '{diretorio_base}': {error}")
72
- sys.exit(1)
73
- except OSError as error:
74
- logger.error(f"Erro do sistema ao salvar CSV em '{diretorio_base}': {error}")
75
- sys.exit(1)
76
- except Exception as error:
77
- logger.error(f"Erro inesperado ao salvar CSV: {error}")
78
- sys.exit(1)
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
- falhar_se_vazio: bool = False
87
- ) -> Optional[str]:
88
- """Executa uma função de extração e salva o resultado como CSV.
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: Nome base para o arquivo CSV (sem extensão).
92
- funcao_extracao: Função que retorna um DataFrame.
93
- data_extracao: Data que será usada no nome do arquivo (ex: "2025_01_19").
94
- diretorio_base: Diretório onde o arquivo será salvo.
95
- falhar_se_vazio: Se True, encerra com sys.exit(1) quando DataFrame for vazio.
96
- Se False, apenas loga warning e retorna None. Defaults to False.
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
- str: Caminho do arquivo salvo, ou None se DataFrame estiver vazio e falhar_se_vazio=False.
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
- SystemExit: Se houver erro na extração, ao salvar o arquivo,
103
- ou se DataFrame for vazio e falhar_se_vazio=True.
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
- >>> from nia_etl_utils.processa_csv import extrair_e_exportar_csv
107
- >>>
108
- >>> def extrair_dados():
109
- ... return pd.DataFrame({"col1": [1, 2, 3]})
110
- >>>
111
- >>> caminho = extrair_e_exportar_csv(
112
- ... nome_extracao="dados_clientes",
113
- ... funcao_extracao=extrair_dados,
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
- try:
119
- logger.info(f"Iniciando extração: {nome_extracao}")
212
+ logger.info(f"Iniciando extração: {nome_extracao}")
120
213
 
121
- # Executa função de extração
214
+ # Executa função de extração
215
+ try:
122
216
  df_extraido = funcao_extracao()
123
-
124
- # Valida resultado
125
- if df_extraido is None or df_extraido.empty:
126
- mensagem = f"Nenhum dado retornado para extração '{nome_extracao}'"
127
-
128
- if falhar_se_vazio:
129
- logger.error(mensagem)
130
- sys.exit(1)
131
- else:
132
- logger.warning(mensagem)
133
- return None
134
-
135
- # Exporta para CSV
136
- caminho = exportar_para_csv(
137
- df=df_extraido,
138
- nome_arquivo=nome_extracao,
139
- data_extracao=data_extracao,
140
- diretorio_base=diretorio_base,
141
- )
142
-
143
- logger.success(f"Extração concluída com sucesso: {nome_extracao}")
144
- return caminho
145
-
146
- except Exception as error:
147
- logger.error(f"Erro ao extrair ou salvar '{nome_extracao}': {error}")
148
- sys.exit(1)
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
- falhar_se_vazio: bool = False
156
- ) -> dict[str, Optional[str]]:
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 com 'nome' e 'funcao' para cada extração.
161
- data_extracao: Data que será usada nos nomes dos arquivos.
162
- diretorio_base: Diretório onde os arquivos serão salvos.
163
- falhar_se_vazio: Se True, encerra quando algum DataFrame for vazio.
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
- dict: Mapeamento {nome_extracao: caminho_arquivo} para cada extração bem-sucedida.
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
- >>> from nia_etl_utils.processa_csv import exportar_multiplos_csv
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
- resultados = {}
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
- caminho = extrair_e_exportar_csv(
197
- nome_extracao=nome,
198
- funcao_extracao=funcao,
199
- data_extracao=data_extracao,
200
- diretorio_base=diretorio_base,
201
- falhar_se_vazio=falhar_se_vazio
202
- )
203
-
204
- resultados[nome] = caminho
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
- sucesso = sum(1 for v in resultados.values() if v is not None)
207
- logger.info(f"Extrações concluídas: {sucesso}/{len(extractions)} bem-sucedidas")
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 resultados
352
+ return lote