nia-etl-utils 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (31) hide show
  1. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/PKG-INFO +4 -5
  2. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/README.md +3 -4
  3. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/pyproject.toml +1 -1
  4. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/__init__.py +32 -31
  5. nia_etl_utils-0.2.2/src/nia_etl_utils/exceptions.py +237 -0
  6. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/ocr.py +12 -86
  7. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/PKG-INFO +4 -5
  8. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_ocr.py +5 -4
  9. nia_etl_utils-0.2.1/src/nia_etl_utils/exceptions.py +0 -394
  10. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/setup.cfg +0 -0
  11. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/setup.py +0 -0
  12. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/config.py +0 -0
  13. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/database.py +0 -0
  14. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/email_smtp.py +0 -0
  15. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/env_config.py +0 -0
  16. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/limpeza_pastas.py +0 -0
  17. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/logger_config.py +0 -0
  18. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/processa_csv.py +0 -0
  19. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/processa_csv_paralelo.py +0 -0
  20. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils/results.py +0 -0
  21. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/SOURCES.txt +0 -0
  22. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/dependency_links.txt +0 -0
  23. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/requires.txt +0 -0
  24. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/top_level.txt +0 -0
  25. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_database.py +0 -0
  26. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_email_smtp.py +0 -0
  27. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_env_config.py +0 -0
  28. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_limpeza_pastas.py +0 -0
  29. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_logger_config.py +0 -0
  30. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_processa_csv.py +0 -0
  31. {nia_etl_utils-0.2.1 → nia_etl_utils-0.2.2}/tests/test_processa_csv_paralelo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nia-etl-utils
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Utilitários compartilhados para pipelines ETL do NIA/MPRJ
5
5
  Author-email: Nícolas Esmael <nicolas.esmael@mprj.mp.br>
6
6
  License: MIT
@@ -51,7 +51,7 @@ Desenvolvida para eliminar duplicação de código, padronizar boas práticas e
51
51
 
52
52
  ---
53
53
 
54
- ## Novidades da v0.2.1
54
+ ## Novidades da v0.2.2
55
55
 
56
56
  - **Módulo OCR** (`executar_ocr`) — processamento de OCR via API IntelliDoc com suporte a PDF, imagens e detecção automática de formato
57
57
  - **Exceções de OCR** (`OcrError`, `OcrSubmissaoError`, `OcrProcessamentoError`, `OcrTimeoutError`) — tratamento granular de erros de OCR
@@ -444,8 +444,7 @@ A API processa documentos de forma assíncrona:
444
444
  **Formatos suportados:** PDF, JPG, PNG, GIF, BMP, TIFF (detecção automática via magic bytes)
445
445
 
446
446
  ```python
447
- from nia_etl_utils import executar_ocr
448
- from nia_etl_utils.ocr import OcrError, OcrTimeoutError
447
+ from nia_etl_utils import executar_ocr, OcrError, OcrTimeoutError
449
448
 
450
449
  # Uso básico com variável de ambiente
451
450
  with open("documento.pdf", "rb") as f:
@@ -683,7 +682,7 @@ Este projeto usa [Semantic Versioning](https://semver.org/):
683
682
  - **MINOR**: Novas funcionalidades (retrocompatíveis)
684
683
  - **PATCH**: Correções de bugs
685
684
 
686
- **Versão atual:** `v0.2.1`
685
+ **Versão atual:** `v0.2.2`
687
686
 
688
687
  ---
689
688
 
@@ -8,7 +8,7 @@ Desenvolvida para eliminar duplicação de código, padronizar boas práticas e
8
8
 
9
9
  ---
10
10
 
11
- ## Novidades da v0.2.1
11
+ ## Novidades da v0.2.2
12
12
 
13
13
  - **Módulo OCR** (`executar_ocr`) — processamento de OCR via API IntelliDoc com suporte a PDF, imagens e detecção automática de formato
14
14
  - **Exceções de OCR** (`OcrError`, `OcrSubmissaoError`, `OcrProcessamentoError`, `OcrTimeoutError`) — tratamento granular de erros de OCR
@@ -401,8 +401,7 @@ A API processa documentos de forma assíncrona:
401
401
  **Formatos suportados:** PDF, JPG, PNG, GIF, BMP, TIFF (detecção automática via magic bytes)
402
402
 
403
403
  ```python
404
- from nia_etl_utils import executar_ocr
405
- from nia_etl_utils.ocr import OcrError, OcrTimeoutError
404
+ from nia_etl_utils import executar_ocr, OcrError, OcrTimeoutError
406
405
 
407
406
  # Uso básico com variável de ambiente
408
407
  with open("documento.pdf", "rb") as f:
@@ -640,7 +639,7 @@ Este projeto usa [Semantic Versioning](https://semver.org/):
640
639
  - **MINOR**: Novas funcionalidades (retrocompatíveis)
641
640
  - **PATCH**: Correções de bugs
642
641
 
643
- **Versão atual:** `v0.2.1`
642
+ **Versão atual:** `v0.2.2`
644
643
 
645
644
  ---
646
645
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nia-etl-utils"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Utilitários compartilhados para pipelines ETL do NIA/MPRJ"
9
9
  authors = [
10
10
  {name = "Nícolas Esmael", email = "nicolas.esmael@mprj.mp.br"}
@@ -20,8 +20,9 @@ Exemplo de uso:
20
20
  conectar_postgresql_nia,
21
21
  exportar_para_csv,
22
22
  PostgresConfig,
23
+ ConexaoError,
24
+ OcrError,
23
25
  )
24
- from nia_etl_utils.exceptions import ConexaoError, ExtracaoVaziaError
25
26
 
26
27
  # Configuração
27
28
  configurar_logger_padrao_nia("meu_pipeline")
@@ -33,39 +34,25 @@ Exemplo de uso:
33
34
  dados = conn.cursor.fetchall()
34
35
  except ConexaoError as e:
35
36
  logger.error(f"Falha na conexão: {e}")
36
- sys.exit(1) # decisão do CHAMADOR
37
-
38
- # Ou com configuração explícita (para testes)
39
- config = PostgresConfig(
40
- host="localhost",
41
- port="5432",
42
- database="teste",
43
- user="user",
44
- password="pass"
45
- )
46
- with conectar_postgresql(config) as conn:
47
- # ...
37
+ sys.exit(1)
48
38
 
49
39
  Exemplo de OCR:
50
40
 
51
- from nia_etl_utils import executar_ocr
52
- from nia_etl_utils.ocr import OcrError
41
+ from nia_etl_utils import executar_ocr, OcrError, OcrTimeoutError
53
42
 
54
43
  try:
55
44
  resultado = executar_ocr(blob_bytes, url_base="INTELLIDOC_URL")
56
45
  texto = resultado["full_text"]
57
46
  qualidade = resultado["overall_quality"]
47
+ except OcrTimeoutError as e:
48
+ logger.error(f"Timeout no OCR: {e}")
58
49
  except OcrError as e:
59
50
  logger.error(f"Falha no OCR: {e}")
60
51
  """
61
52
 
62
- __version__ = "0.2.1"
53
+ __version__ = "0.2.2"
63
54
  __author__ = "Nícolas Galdino Esmael"
64
55
 
65
- # =============================================================================
66
- # EXCEÇÕES - Importar primeiro para uso em type hints
67
- # =============================================================================
68
-
69
56
  # =============================================================================
70
57
  # CONFIGURAÇÕES (Dataclasses)
71
58
  # =============================================================================
@@ -76,8 +63,9 @@ from .config import (
76
63
  SmtpConfig,
77
64
  )
78
65
 
79
- # Database - Funções core
80
- # Database - Wrappers de conveniência
66
+ # =============================================================================
67
+ # DATABASE
68
+ # =============================================================================
81
69
  from .database import (
82
70
  conectar_oracle,
83
71
  conectar_oracle_ouvidorias,
@@ -89,7 +77,9 @@ from .database import (
89
77
  obter_engine_postgresql_opengeo,
90
78
  )
91
79
 
92
- # Email
80
+ # =============================================================================
81
+ # EMAIL
82
+ # =============================================================================
93
83
  from .email_smtp import (
94
84
  enviar_email,
95
85
  enviar_email_smtp,
@@ -99,13 +89,16 @@ from .email_smtp import (
99
89
  # =============================================================================
100
90
  # FUNÇÕES UTILITÁRIAS
101
91
  # =============================================================================
102
- # Configuração de ambiente
103
92
  from .env_config import (
104
93
  obter_variavel_env,
105
94
  obter_variavel_env_bool,
106
95
  obter_variavel_env_int,
107
96
  obter_variavel_env_lista,
108
97
  )
98
+
99
+ # =============================================================================
100
+ # EXCEÇÕES
101
+ # =============================================================================
109
102
  from .exceptions import (
110
103
  # Arquivos
111
104
  ArquivoError,
@@ -125,41 +118,44 @@ from .exceptions import (
125
118
  LeituraArquivoError,
126
119
  # Base
127
120
  NiaEtlError,
121
+ # OCR
122
+ OcrError,
123
+ OcrProcessamentoError,
124
+ OcrSubmissaoError,
125
+ OcrTimeoutError,
128
126
  ProcessamentoError,
129
127
  SmtpError,
130
128
  # Validação
131
129
  ValidacaoError,
132
130
  VariavelAmbienteError,
133
131
  )
134
-
135
- # Manipulação de arquivos
136
132
  from .limpeza_pastas import (
137
133
  criar_pasta_se_nao_existir,
138
134
  limpar_pasta,
139
135
  listar_arquivos,
140
136
  remover_pasta_recursivamente,
141
137
  )
142
-
143
- # Logging
144
138
  from .logger_config import (
145
139
  configurar_logger,
146
140
  configurar_logger_padrao_nia,
147
141
  remover_handlers_existentes,
148
142
  )
149
143
 
144
+ # =============================================================================
150
145
  # OCR
146
+ # =============================================================================
151
147
  from .ocr import (
152
148
  executar_ocr,
153
149
  )
154
150
 
155
- # Processamento CSV
151
+ # =============================================================================
152
+ # CSV
153
+ # =============================================================================
156
154
  from .processa_csv import (
157
155
  exportar_multiplos_csv,
158
156
  exportar_para_csv,
159
157
  extrair_e_exportar_csv,
160
158
  )
161
-
162
- # Processamento CSV Paralelo
163
159
  from .processa_csv_paralelo import (
164
160
  calcular_chunksize,
165
161
  processar_csv_paralelo,
@@ -204,6 +200,11 @@ __all__ = [
204
200
  "EmailError",
205
201
  "DestinatarioError",
206
202
  "SmtpError",
203
+ # Exceções - OCR
204
+ "OcrError",
205
+ "OcrSubmissaoError",
206
+ "OcrProcessamentoError",
207
+ "OcrTimeoutError",
207
208
  # Exceções - Validação
208
209
  "ValidacaoError",
209
210
  # Configurações
@@ -0,0 +1,237 @@
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
+ ├── OcrError
22
+ │ ├── OcrSubmissaoError
23
+ │ ├── OcrProcessamentoError
24
+ │ └── OcrTimeoutError
25
+ └── ValidacaoError
26
+
27
+ Examples:
28
+ Capturando erros específicos:
29
+
30
+ >>> from nia_etl_utils import ConexaoError, OcrTimeoutError
31
+ >>>
32
+ >>> try:
33
+ ... resultado = executar_ocr(documento)
34
+ ... except OcrTimeoutError as e:
35
+ ... logger.error(f"Timeout no OCR: {e}")
36
+
37
+ Capturando qualquer erro do pacote:
38
+
39
+ >>> from nia_etl_utils import NiaEtlError
40
+ >>>
41
+ >>> try:
42
+ ... executar_pipeline()
43
+ ... except NiaEtlError as e:
44
+ ... logger.error(f"Erro no pipeline: {e}")
45
+ ... sys.exit(1)
46
+ """
47
+
48
+
49
+ class NiaEtlError(Exception):
50
+ """Exceção base para todos os erros do pacote nia_etl_utils.
51
+
52
+ Todas as exceções customizadas do pacote herdam desta classe,
53
+ permitindo captura genérica quando necessário.
54
+
55
+ Attributes:
56
+ message: Descrição do erro.
57
+ details: Informações adicionais opcionais sobre o erro.
58
+
59
+ Examples:
60
+ >>> try:
61
+ ... raise NiaEtlError("Algo deu errado", details={"codigo": 123})
62
+ ... except NiaEtlError as e:
63
+ ... print(e.message)
64
+ ... print(e.details)
65
+ Algo deu errado
66
+ {'codigo': 123}
67
+ """
68
+
69
+ def __init__(self, message: str, details: dict | None = None):
70
+ self.message = message
71
+ self.details = details or {}
72
+ super().__init__(self.message)
73
+
74
+ def __str__(self) -> str:
75
+ if self.details:
76
+ return f"{self.message} | Detalhes: {self.details}"
77
+ return self.message
78
+
79
+
80
+ # =============================================================================
81
+ # CONFIGURAÇÃO
82
+ # =============================================================================
83
+
84
+
85
+ class ConfiguracaoError(NiaEtlError):
86
+ """Erro de configuração do sistema."""
87
+
88
+ pass
89
+
90
+
91
+ class VariavelAmbienteError(ConfiguracaoError):
92
+ """Variável de ambiente ausente ou inválida."""
93
+
94
+ def __init__(self, nome_variavel: str):
95
+ self.nome_variavel = nome_variavel
96
+ super().__init__(
97
+ f"Variável de ambiente '{nome_variavel}' não encontrada "
98
+ f"e nenhum valor padrão foi fornecido",
99
+ details={"variavel": nome_variavel}
100
+ )
101
+
102
+
103
+ # =============================================================================
104
+ # DATABASE
105
+ # =============================================================================
106
+
107
+
108
+ class DatabaseError(NiaEtlError):
109
+ """Erro base para operações de banco de dados."""
110
+
111
+ pass
112
+
113
+
114
+ class ConexaoError(DatabaseError):
115
+ """Falha ao estabelecer conexão com banco de dados."""
116
+
117
+ pass
118
+
119
+
120
+ # =============================================================================
121
+ # ARQUIVOS E DIRETÓRIOS
122
+ # =============================================================================
123
+
124
+
125
+ class ArquivoError(NiaEtlError):
126
+ """Erro base para operações de arquivo e diretório."""
127
+
128
+ pass
129
+
130
+
131
+ class EscritaArquivoError(ArquivoError):
132
+ """Falha ao escrever arquivo."""
133
+
134
+ pass
135
+
136
+
137
+ class LeituraArquivoError(ArquivoError):
138
+ """Falha ao ler arquivo."""
139
+
140
+ pass
141
+
142
+
143
+ class DiretorioError(ArquivoError):
144
+ """Falha em operação de diretório."""
145
+
146
+ pass
147
+
148
+
149
+ # =============================================================================
150
+ # EXTRAÇÃO E PROCESSAMENTO
151
+ # =============================================================================
152
+
153
+
154
+ class ExtracaoError(NiaEtlError):
155
+ """Erro base para operações de extração de dados."""
156
+
157
+ pass
158
+
159
+
160
+ class ExtracaoVaziaError(ExtracaoError):
161
+ """Extração retornou DataFrame vazio ou None."""
162
+
163
+ def __init__(self, nome_extracao: str):
164
+ self.nome_extracao = nome_extracao
165
+ super().__init__(
166
+ f"Nenhum dado retornado para extração '{nome_extracao}'",
167
+ details={"extracao": nome_extracao}
168
+ )
169
+
170
+
171
+ class ProcessamentoError(ExtracaoError):
172
+ """Erro durante processamento de dados."""
173
+
174
+ pass
175
+
176
+
177
+ # =============================================================================
178
+ # EMAIL
179
+ # =============================================================================
180
+
181
+
182
+ class EmailError(NiaEtlError):
183
+ """Erro base para operações de email."""
184
+
185
+ pass
186
+
187
+
188
+ class DestinatarioError(EmailError):
189
+ """Erro relacionado a destinatários de email."""
190
+
191
+ pass
192
+
193
+
194
+ class SmtpError(EmailError):
195
+ """Erro de comunicação com servidor SMTP."""
196
+
197
+ pass
198
+
199
+
200
+ # =============================================================================
201
+ # OCR
202
+ # =============================================================================
203
+
204
+
205
+ class OcrError(NiaEtlError):
206
+ """Erro base para operações de OCR."""
207
+
208
+ pass
209
+
210
+
211
+ class OcrSubmissaoError(OcrError):
212
+ """Falha ao submeter documento para OCR."""
213
+
214
+ pass
215
+
216
+
217
+ class OcrProcessamentoError(OcrError):
218
+ """Falha no processamento do documento pela API."""
219
+
220
+ pass
221
+
222
+
223
+ class OcrTimeoutError(OcrError):
224
+ """Timeout aguardando resultado do OCR."""
225
+
226
+ pass
227
+
228
+
229
+ # =============================================================================
230
+ # VALIDAÇÃO
231
+ # =============================================================================
232
+
233
+
234
+ class ValidacaoError(NiaEtlError):
235
+ """Erro de validação de parâmetros ou dados."""
236
+
237
+ pass
@@ -10,23 +10,13 @@ A API IntelliDoc processa documentos de forma assíncrona:
10
10
  Example:
11
11
  Uso básico com variável de ambiente:
12
12
 
13
- >>> from nia_etl_utils.ocr import executar_ocr
13
+ >>> from nia_etl_utils import executar_ocr, OcrError
14
14
  >>>
15
- >>> with open("documento.pdf", "rb") as f:
16
- ... resultado = executar_ocr(
17
- ... conteudo=f.read(),
18
- ... url_base="INTELLIDOC_URL", # env var
19
- ... )
20
- >>> print(resultado["full_text"])
21
-
22
- Uso com URL direta e configurações customizadas:
23
-
24
- >>> resultado = executar_ocr(
25
- ... conteudo=blob_bytes,
26
- ... url_base="http://google.com",
27
- ... timeout_polling=600,
28
- ... max_tentativas=5,
29
- ... )
15
+ >>> try:
16
+ ... resultado = executar_ocr(blob_bytes, url_base="INTELLIDOC_URL")
17
+ ... print(resultado["full_text"])
18
+ ... except OcrError as e:
19
+ ... logger.error(f"Falha no OCR: {e}")
30
20
  """
31
21
 
32
22
  import time
@@ -35,70 +25,11 @@ import requests
35
25
  from loguru import logger
36
26
 
37
27
  from .env_config import obter_variavel_env
38
- from .exceptions import NiaEtlError
39
-
40
- # =============================================================================
41
- # EXCEÇÕES
42
- # =============================================================================
43
-
44
-
45
- class OcrError(NiaEtlError):
46
- """Erro base para operações de OCR.
47
-
48
- Examples:
49
- >>> raise OcrError("Falha no processamento OCR")
50
- """
51
-
52
- pass
53
-
54
-
55
- class OcrSubmissaoError(OcrError):
56
- """Falha ao submeter documento para OCR.
57
-
58
- Levantado quando não é possível enviar o documento para a API,
59
- seja por problemas de rede, timeout ou resposta inválida.
60
-
61
- Examples:
62
- >>> raise OcrSubmissaoError(
63
- ... "Timeout ao submeter documento",
64
- ... details={"tentativas": 3, "status": 504}
65
- ... )
66
- """
67
-
68
- pass
69
-
70
-
71
- class OcrProcessamentoError(OcrError):
72
- """Falha no processamento do documento pela API.
73
-
74
- Levantado quando a API retorna status FAILURE ou REVOKED,
75
- indicando que o documento não pôde ser processado.
76
-
77
- Examples:
78
- >>> raise OcrProcessamentoError(
79
- ... "Documento corrompido",
80
- ... details={"document_id": "abc-123", "erro_api": "invalid format"}
81
- ... )
82
- """
83
-
84
- pass
85
-
86
-
87
- class OcrTimeoutError(OcrError):
88
- """Timeout aguardando resultado do OCR.
89
-
90
- Levantado quando o tempo máximo de polling é atingido
91
- sem que a API retorne um resultado final.
92
-
93
- Examples:
94
- >>> raise OcrTimeoutError(
95
- ... "Timeout após 300s",
96
- ... details={"document_id": "abc-123", "ultimo_status": "PENDING"}
97
- ... )
98
- """
99
-
100
- pass
101
-
28
+ from .exceptions import (
29
+ OcrProcessamentoError,
30
+ OcrSubmissaoError,
31
+ OcrTimeoutError,
32
+ )
102
33
 
103
34
  # =============================================================================
104
35
  # CONSTANTES
@@ -369,15 +300,10 @@ def executar_ocr(
369
300
 
370
301
  >>> resultado = executar_ocr(
371
302
  ... conteudo=pdf_bytes,
372
- ... url_base="http://google.com",
303
+ ... url_base="http://intellidoc.mprj.mp.br",
373
304
  ... timeout_polling=600,
374
305
  ... )
375
306
  >>> print(f"Qualidade: {resultado['overall_quality']}")
376
-
377
- Acessando detalhes das páginas:
378
-
379
- >>> for page in resultado["pages"]:
380
- ... print(f"Página {page['page_number']}: {page['extraction_method']}")
381
307
  """
382
308
  conteudo_bytes = _normalizar_para_bytes(conteudo)
383
309
  extensao = _detectar_extensao(conteudo_bytes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nia-etl-utils
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Utilitários compartilhados para pipelines ETL do NIA/MPRJ
5
5
  Author-email: Nícolas Esmael <nicolas.esmael@mprj.mp.br>
6
6
  License: MIT
@@ -51,7 +51,7 @@ Desenvolvida para eliminar duplicação de código, padronizar boas práticas e
51
51
 
52
52
  ---
53
53
 
54
- ## Novidades da v0.2.1
54
+ ## Novidades da v0.2.2
55
55
 
56
56
  - **Módulo OCR** (`executar_ocr`) — processamento de OCR via API IntelliDoc com suporte a PDF, imagens e detecção automática de formato
57
57
  - **Exceções de OCR** (`OcrError`, `OcrSubmissaoError`, `OcrProcessamentoError`, `OcrTimeoutError`) — tratamento granular de erros de OCR
@@ -444,8 +444,7 @@ A API processa documentos de forma assíncrona:
444
444
  **Formatos suportados:** PDF, JPG, PNG, GIF, BMP, TIFF (detecção automática via magic bytes)
445
445
 
446
446
  ```python
447
- from nia_etl_utils import executar_ocr
448
- from nia_etl_utils.ocr import OcrError, OcrTimeoutError
447
+ from nia_etl_utils import executar_ocr, OcrError, OcrTimeoutError
449
448
 
450
449
  # Uso básico com variável de ambiente
451
450
  with open("documento.pdf", "rb") as f:
@@ -683,7 +682,7 @@ Este projeto usa [Semantic Versioning](https://semver.org/):
683
682
  - **MINOR**: Novas funcionalidades (retrocompatíveis)
684
683
  - **PATCH**: Correções de bugs
685
684
 
686
- **Versão atual:** `v0.2.1`
685
+ **Versão atual:** `v0.2.2`
687
686
 
688
687
  ---
689
688
 
@@ -4,15 +4,18 @@ from unittest.mock import MagicMock, patch
4
4
 
5
5
  import pytest
6
6
 
7
- from nia_etl_utils.ocr import (
7
+ from nia_etl_utils import (
8
+ NiaEtlError,
8
9
  OcrError,
9
10
  OcrProcessamentoError,
10
11
  OcrSubmissaoError,
11
12
  OcrTimeoutError,
13
+ executar_ocr,
14
+ )
15
+ from nia_etl_utils.ocr import (
12
16
  _detectar_extensao,
13
17
  _normalizar_para_bytes,
14
18
  _resolver_url_base,
15
- executar_ocr,
16
19
  )
17
20
 
18
21
 
@@ -400,8 +403,6 @@ class TestExcecoesOcr:
400
403
  """Testes para exceções de OCR."""
401
404
 
402
405
  def test_ocr_error_herda_nia_etl_error(self) -> None:
403
- from nia_etl_utils.exceptions import NiaEtlError
404
-
405
406
  erro = OcrError("teste")
406
407
  assert isinstance(erro, NiaEtlError)
407
408
 
@@ -1,394 +0,0 @@
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
- ├── OcrError
22
- │ ├── OcrSubmissaoError
23
- │ ├── OcrProcessamentoError
24
- │ └── OcrTimeoutError
25
- └── ValidacaoError
26
-
27
- Examples:
28
- Capturando erros específicos:
29
-
30
- >>> from nia_etl_utils.exceptions import ConexaoError, ExtracaoVaziaError
31
- >>>
32
- >>> try:
33
- ... conn = conectar_postgresql(config)
34
- ... except ConexaoError as e:
35
- ... logger.error(f"Falha na conexão: {e}")
36
- ... # tratamento específico
37
-
38
- Capturando qualquer erro do pacote:
39
-
40
- >>> from nia_etl_utils.exceptions import NiaEtlError
41
- >>>
42
- >>> try:
43
- ... executar_pipeline()
44
- ... except NiaEtlError as e:
45
- ... logger.error(f"Erro no pipeline: {e}")
46
- ... sys.exit(1) # decisão do CHAMADOR
47
- """
48
-
49
-
50
- class NiaEtlError(Exception):
51
- """Exceção base para todos os erros do pacote nia_etl_utils.
52
-
53
- Todas as exceções customizadas do pacote herdam desta classe,
54
- permitindo captura genérica quando necessário.
55
-
56
- Attributes:
57
- message: Descrição do erro.
58
- details: Informações adicionais opcionais sobre o erro.
59
-
60
- Examples:
61
- >>> try:
62
- ... raise NiaEtlError("Algo deu errado", details={"codigo": 123})
63
- ... except NiaEtlError as e:
64
- ... print(e.message)
65
- ... print(e.details)
66
- Algo deu errado
67
- {'codigo': 123}
68
- """
69
-
70
- def __init__(self, message: str, details: dict | None = None):
71
- self.message = message
72
- self.details = details or {}
73
- super().__init__(self.message)
74
-
75
- def __str__(self) -> str:
76
- if self.details:
77
- return f"{self.message} | Detalhes: {self.details}"
78
- return self.message
79
-
80
-
81
- # =============================================================================
82
- # CONFIGURAÇÃO
83
- # =============================================================================
84
-
85
-
86
- class ConfiguracaoError(NiaEtlError):
87
- """Erro de configuração do sistema.
88
-
89
- Levantado quando há problemas com configurações necessárias
90
- para o funcionamento do pacote.
91
-
92
- Examples:
93
- >>> raise ConfiguracaoError("Configuração inválida para conexão")
94
- """
95
-
96
- pass
97
-
98
-
99
- class VariavelAmbienteError(ConfiguracaoError):
100
- """Variável de ambiente ausente ou inválida.
101
-
102
- Levantado quando uma variável de ambiente obrigatória não está
103
- definida e nenhum valor padrão foi fornecido.
104
-
105
- Attributes:
106
- nome_variavel: Nome da variável de ambiente que causou o erro.
107
-
108
- Examples:
109
- >>> raise VariavelAmbienteError("DB_HOST")
110
- """
111
-
112
- def __init__(self, nome_variavel: str):
113
- self.nome_variavel = nome_variavel
114
- super().__init__(
115
- f"Variável de ambiente '{nome_variavel}' não encontrada "
116
- f"e nenhum valor padrão foi fornecido",
117
- details={"variavel": nome_variavel}
118
- )
119
-
120
-
121
- # =============================================================================
122
- # DATABASE
123
- # =============================================================================
124
-
125
-
126
- class DatabaseError(NiaEtlError):
127
- """Erro base para operações de banco de dados.
128
-
129
- Examples:
130
- >>> raise DatabaseError("Falha na operação de banco de dados")
131
- """
132
-
133
- pass
134
-
135
-
136
- class ConexaoError(DatabaseError):
137
- """Falha ao estabelecer conexão com banco de dados.
138
-
139
- Levantado quando não é possível conectar ao banco de dados,
140
- seja por credenciais inválidas, host inacessível ou outros
141
- problemas de conectividade.
142
-
143
- Examples:
144
- >>> raise ConexaoError(
145
- ... "Timeout ao conectar",
146
- ... details={"host": "localhost", "port": 5432}
147
- ... )
148
- """
149
-
150
- pass
151
-
152
-
153
- # =============================================================================
154
- # ARQUIVOS E DIRETÓRIOS
155
- # =============================================================================
156
-
157
-
158
- class ArquivoError(NiaEtlError):
159
- """Erro base para operações de arquivo e diretório.
160
-
161
- Examples:
162
- >>> raise ArquivoError("Operação de arquivo falhou")
163
- """
164
-
165
- pass
166
-
167
-
168
- class EscritaArquivoError(ArquivoError):
169
- """Falha ao escrever arquivo.
170
-
171
- Levantado quando não é possível criar ou escrever em um arquivo,
172
- seja por falta de permissão, disco cheio ou caminho inválido.
173
-
174
- Examples:
175
- >>> raise EscritaArquivoError(
176
- ... "Sem permissão para escrita",
177
- ... details={"caminho": "/etc/arquivo.csv"}
178
- ... )
179
- """
180
-
181
- pass
182
-
183
-
184
- class LeituraArquivoError(ArquivoError):
185
- """Falha ao ler arquivo.
186
-
187
- Levantado quando não é possível ler um arquivo, seja porque
188
- ele não existe, não há permissão ou está corrompido.
189
-
190
- Examples:
191
- >>> raise LeituraArquivoError(
192
- ... "Arquivo não encontrado",
193
- ... details={"caminho": "/tmp/dados.csv"}
194
- ... )
195
- """
196
-
197
- pass
198
-
199
-
200
- class DiretorioError(ArquivoError):
201
- """Falha em operação de diretório.
202
-
203
- Levantado quando não é possível criar, limpar ou remover
204
- um diretório.
205
-
206
- Examples:
207
- >>> raise DiretorioError(
208
- ... "Sem permissão para criar diretório",
209
- ... details={"caminho": "/root/dados"}
210
- ... )
211
- """
212
-
213
- pass
214
-
215
-
216
- # =============================================================================
217
- # EXTRAÇÃO E PROCESSAMENTO
218
- # =============================================================================
219
-
220
-
221
- class ExtracaoError(NiaEtlError):
222
- """Erro base para operações de extração de dados.
223
-
224
- Examples:
225
- >>> raise ExtracaoError("Falha na extração de dados")
226
- """
227
-
228
- pass
229
-
230
-
231
- class ExtracaoVaziaError(ExtracaoError):
232
- """Extração retornou DataFrame vazio ou None.
233
-
234
- Levantado quando uma função de extração não retorna dados.
235
- Pode ser esperado em alguns contextos (extração incremental
236
- sem novos dados) ou indicar um problema.
237
-
238
- Attributes:
239
- nome_extracao: Identificador da extração que falhou.
240
-
241
- Examples:
242
- >>> raise ExtracaoVaziaError("clientes_novos")
243
- """
244
-
245
- def __init__(self, nome_extracao: str):
246
- self.nome_extracao = nome_extracao
247
- super().__init__(
248
- f"Nenhum dado retornado para extração '{nome_extracao}'",
249
- details={"extracao": nome_extracao}
250
- )
251
-
252
-
253
- class ProcessamentoError(ExtracaoError):
254
- """Erro durante processamento de dados.
255
-
256
- Levantado quando há falha durante transformação ou
257
- processamento de dados.
258
-
259
- Examples:
260
- >>> raise ProcessamentoError(
261
- ... "Falha ao processar chunk",
262
- ... details={"chunk": 5, "erro": "memória insuficiente"}
263
- ... )
264
- """
265
-
266
- pass
267
-
268
-
269
- # =============================================================================
270
- # EMAIL
271
- # =============================================================================
272
-
273
-
274
- class EmailError(NiaEtlError):
275
- """Erro base para operações de email.
276
-
277
- Examples:
278
- >>> raise EmailError("Falha no envio de email")
279
- """
280
-
281
- pass
282
-
283
-
284
- class DestinatarioError(EmailError):
285
- """Erro relacionado a destinatários de email.
286
-
287
- Levantado quando não há destinatários configurados ou
288
- quando os destinatários são inválidos.
289
-
290
- Examples:
291
- >>> raise DestinatarioError("Nenhum destinatário configurado")
292
- """
293
-
294
- pass
295
-
296
-
297
- class SmtpError(EmailError):
298
- """Erro de comunicação com servidor SMTP.
299
-
300
- Levantado quando há falha na conexão ou comunicação
301
- com o servidor de email.
302
-
303
- Examples:
304
- >>> raise SmtpError(
305
- ... "Conexão recusada",
306
- ... details={"servidor": "smtp.empresa.com", "porta": 587}
307
- ... )
308
- """
309
-
310
- pass
311
-
312
-
313
- # =============================================================================
314
- # OCR
315
- # =============================================================================
316
-
317
-
318
- class OcrError(NiaEtlError):
319
- """Erro base para operações de OCR.
320
-
321
- Examples:
322
- >>> raise OcrError("Falha no processamento OCR")
323
- """
324
-
325
- pass
326
-
327
-
328
- class OcrSubmissaoError(OcrError):
329
- """Falha ao submeter documento para OCR.
330
-
331
- Levantado quando não é possível enviar o documento para a API,
332
- seja por problemas de rede, timeout ou resposta inválida.
333
-
334
- Examples:
335
- >>> raise OcrSubmissaoError(
336
- ... "Timeout ao submeter documento",
337
- ... details={"tentativas": 3, "status": 504}
338
- ... )
339
- """
340
-
341
- pass
342
-
343
-
344
- class OcrProcessamentoError(OcrError):
345
- """Falha no processamento do documento pela API.
346
-
347
- Levantado quando a API retorna status FAILURE ou REVOKED,
348
- indicando que o documento não pôde ser processado.
349
-
350
- Examples:
351
- >>> raise OcrProcessamentoError(
352
- ... "Documento corrompido",
353
- ... details={"document_id": "abc-123", "erro_api": "invalid format"}
354
- ... )
355
- """
356
-
357
- pass
358
-
359
-
360
- class OcrTimeoutError(OcrError):
361
- """Timeout aguardando resultado do OCR.
362
-
363
- Levantado quando o tempo máximo de polling é atingido
364
- sem que a API retorne um resultado final.
365
-
366
- Examples:
367
- >>> raise OcrTimeoutError(
368
- ... "Timeout após 300s",
369
- ... details={"document_id": "abc-123", "ultimo_status": "PENDING"}
370
- ... )
371
- """
372
-
373
- pass
374
-
375
-
376
- # =============================================================================
377
- # VALIDAÇÃO
378
- # =============================================================================
379
-
380
-
381
- class ValidacaoError(NiaEtlError):
382
- """Erro de validação de parâmetros ou dados.
383
-
384
- Levantado quando parâmetros fornecidos são inválidos
385
- ou não atendem aos requisitos esperados.
386
-
387
- Examples:
388
- >>> raise ValidacaoError(
389
- ... "Nome do arquivo não pode ser vazio",
390
- ... details={"parametro": "nome_arquivo", "valor": ""}
391
- ... )
392
- """
393
-
394
- pass
File without changes
File without changes