nia-etl-utils 0.2.0__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.
- nia_etl_utils-0.2.2/PKG-INFO +722 -0
- nia_etl_utils-0.2.2/README.md +679 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/pyproject.toml +3 -1
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/__init__.py +50 -29
- nia_etl_utils-0.2.2/src/nia_etl_utils/exceptions.py +237 -0
- nia_etl_utils-0.2.2/src/nia_etl_utils/ocr.py +327 -0
- nia_etl_utils-0.2.2/src/nia_etl_utils.egg-info/PKG-INFO +722 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/SOURCES.txt +2 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/requires.txt +2 -0
- nia_etl_utils-0.2.2/tests/test_ocr.py +427 -0
- nia_etl_utils-0.2.0/PKG-INFO +0 -615
- nia_etl_utils-0.2.0/README.md +0 -574
- nia_etl_utils-0.2.0/src/nia_etl_utils/exceptions.py +0 -327
- nia_etl_utils-0.2.0/src/nia_etl_utils.egg-info/PKG-INFO +0 -615
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/setup.cfg +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/setup.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/config.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/database.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/email_smtp.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/env_config.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/limpeza_pastas.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/logger_config.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/processa_csv.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/processa_csv_paralelo.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils/results.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/dependency_links.txt +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/src/nia_etl_utils.egg-info/top_level.txt +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_database.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_email_smtp.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_env_config.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_limpeza_pastas.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_logger_config.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_processa_csv.py +0 -0
- {nia_etl_utils-0.2.0 → nia_etl_utils-0.2.2}/tests/test_processa_csv_paralelo.py +0 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nia-etl-utils
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Utilitários compartilhados para pipelines ETL do NIA/MPRJ
|
|
5
|
+
Author-email: Nícolas Esmael <nicolas.esmael@mprj.mp.br>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://gitlab-dti.mprj.mp.br/nia/etl-nia/nia-etl-utils
|
|
8
|
+
Project-URL: Documentation, https://gitlab-dti.mprj.mp.br/nia/etl-nia/nia-etl-utils/-/blob/main/README.md
|
|
9
|
+
Project-URL: Changelog, https://gitlab-dti.mprj.mp.br/nia/etl-nia/nia-etl-utils/-/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: etl,data-engineering,pipeline,mprj,nia
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: loguru>=0.7.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: cx-Oracle>=8.3.0
|
|
28
|
+
Requires-Dist: psycopg2-binary>=2.9.0
|
|
29
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
30
|
+
Requires-Dist: pandas>=2.0.0
|
|
31
|
+
Requires-Dist: requests>=2.28.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pandas-stubs>=2.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
39
|
+
Provides-Extra: docs
|
|
40
|
+
Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
41
|
+
Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
|
|
42
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "docs"
|
|
43
|
+
|
|
44
|
+
# Módulos Utilitários do NIA
|
|
45
|
+
|
|
46
|
+
## Visão Geral
|
|
47
|
+
|
|
48
|
+
Biblioteca Python centralizada contendo utilitários compartilhados para pipelines ETL do NIA/MPRJ. Consolida funções reutilizáveis para configuração de ambiente, notificações por email, conexões de banco de dados, logging padronizado e processamento de dados.
|
|
49
|
+
|
|
50
|
+
Desenvolvida para eliminar duplicação de código, padronizar boas práticas e facilitar manutenção em todos os projetos de engenharia de dados do NIA.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Novidades da v0.2.2
|
|
55
|
+
|
|
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
|
+
- **Exceções de OCR** (`OcrError`, `OcrSubmissaoError`, `OcrProcessamentoError`, `OcrTimeoutError`) — tratamento granular de erros de OCR
|
|
58
|
+
|
|
59
|
+
## Novidades da v0.2.0
|
|
60
|
+
|
|
61
|
+
- **Dataclasses de configuração** (`PostgresConfig`, `OracleConfig`, `SmtpConfig`, `LogConfig`) — configurações imutáveis e type-safe
|
|
62
|
+
- **Exceções customizadas hierárquicas** — tratamento de erros mais preciso e informativo
|
|
63
|
+
- **Dataclasses de resultado** (`Conexao`, `ResultadoExtracao`, `ResultadoLote`, `ResultadoEmail`)
|
|
64
|
+
- **Funções adicionais de env** — `obter_variavel_env_int`, `obter_variavel_env_bool`, `obter_variavel_env_lista`
|
|
65
|
+
- **Context managers** para conexões de banco — fechamento automático e seguro
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Estrutura do Projeto
|
|
70
|
+
|
|
71
|
+
```plaintext
|
|
72
|
+
.
|
|
73
|
+
├── src/
|
|
74
|
+
│ └── nia_etl_utils/
|
|
75
|
+
│ ├── __init__.py # Exporta funções principais
|
|
76
|
+
│ ├── config.py # Dataclasses de configuração
|
|
77
|
+
│ ├── exceptions.py # Exceções customizadas
|
|
78
|
+
│ ├── results.py # Dataclasses de resultado
|
|
79
|
+
│ ├── env_config.py # Gerenciamento de variáveis de ambiente
|
|
80
|
+
│ ├── email_smtp.py # Envio de emails via SMTP
|
|
81
|
+
│ ├── database.py # Conexões PostgreSQL e Oracle
|
|
82
|
+
│ ├── logger_config.py # Configuração de logging com Loguru
|
|
83
|
+
│ ├── processa_csv.py # Processamento e exportação de CSV
|
|
84
|
+
│ ├── processa_csv_paralelo.py # Processamento paralelo de CSV grandes
|
|
85
|
+
│ ├── limpeza_pastas.py # Manipulação de arquivos e diretórios
|
|
86
|
+
│ └── ocr.py # OCR via API IntelliDoc
|
|
87
|
+
│
|
|
88
|
+
├── tests/ # Testes unitários
|
|
89
|
+
├── .gitlab-ci.yml # Pipeline CI/CD
|
|
90
|
+
├── pyproject.toml # Configuração do pacote
|
|
91
|
+
└── README.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Módulos Disponíveis
|
|
97
|
+
|
|
98
|
+
### 1. Configuração de Ambiente (`env_config.py`)
|
|
99
|
+
|
|
100
|
+
Gerenciamento robusto de variáveis de ambiente com validação e tipagem.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from nia_etl_utils import (
|
|
104
|
+
obter_variavel_env,
|
|
105
|
+
obter_variavel_env_int,
|
|
106
|
+
obter_variavel_env_bool,
|
|
107
|
+
obter_variavel_env_lista
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# String obrigatória (falha com sys.exit(1) se não existir)
|
|
111
|
+
db_host = obter_variavel_env('DB_POSTGRESQL_HOST')
|
|
112
|
+
|
|
113
|
+
# String opcional com fallback
|
|
114
|
+
porta = obter_variavel_env('DB_PORT', default='5432')
|
|
115
|
+
|
|
116
|
+
# Inteiro
|
|
117
|
+
max_conexoes = obter_variavel_env_int('MAX_CONEXOES', default=10)
|
|
118
|
+
|
|
119
|
+
# Booleano (aceita: true/false, 1/0, yes/no, on/off)
|
|
120
|
+
debug = obter_variavel_env_bool('DEBUG_MODE', default=False)
|
|
121
|
+
|
|
122
|
+
# Lista (separada por vírgula)
|
|
123
|
+
destinatarios = obter_variavel_env_lista('EMAIL_DESTINATARIOS')
|
|
124
|
+
# ['email1@mprj.mp.br', 'email2@mprj.mp.br']
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 2. Dataclasses de Configuração (`config.py`)
|
|
130
|
+
|
|
131
|
+
Configurações imutáveis e type-safe com factory methods.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from nia_etl_utils import PostgresConfig, OracleConfig, SmtpConfig, LogConfig
|
|
135
|
+
|
|
136
|
+
# Configuração explícita (recomendado para testes)
|
|
137
|
+
config = PostgresConfig(
|
|
138
|
+
host="localhost",
|
|
139
|
+
port="5432",
|
|
140
|
+
database="teste",
|
|
141
|
+
user="user",
|
|
142
|
+
password="pass"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Configuração via variáveis de ambiente (recomendado para produção)
|
|
146
|
+
config = PostgresConfig.from_env() # usa DB_POSTGRESQL_*
|
|
147
|
+
config = PostgresConfig.from_env("_OPENGEO") # usa DB_POSTGRESQL_*_OPENGEO
|
|
148
|
+
|
|
149
|
+
# Connection string para SQLAlchemy
|
|
150
|
+
print(config.connection_string)
|
|
151
|
+
# postgresql+psycopg2://user:pass@localhost:5432/teste
|
|
152
|
+
|
|
153
|
+
# Oracle
|
|
154
|
+
oracle_config = OracleConfig.from_env()
|
|
155
|
+
|
|
156
|
+
# SMTP
|
|
157
|
+
smtp_config = SmtpConfig.from_env()
|
|
158
|
+
|
|
159
|
+
# Logging com padrões NIA
|
|
160
|
+
log_config = LogConfig.padrao_nia("meu_pipeline")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### 3. Exceções Customizadas (`exceptions.py`)
|
|
166
|
+
|
|
167
|
+
Hierarquia de exceções para tratamento preciso de erros.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from nia_etl_utils import (
|
|
171
|
+
# Base
|
|
172
|
+
NiaEtlError,
|
|
173
|
+
# Configuração
|
|
174
|
+
ConfiguracaoError,
|
|
175
|
+
VariavelAmbienteError,
|
|
176
|
+
# Database
|
|
177
|
+
DatabaseError,
|
|
178
|
+
ConexaoError,
|
|
179
|
+
# Arquivos
|
|
180
|
+
ArquivoError,
|
|
181
|
+
DiretorioError,
|
|
182
|
+
EscritaArquivoError,
|
|
183
|
+
LeituraArquivoError,
|
|
184
|
+
# Extração
|
|
185
|
+
ExtracaoError,
|
|
186
|
+
ExtracaoVaziaError,
|
|
187
|
+
ProcessamentoError,
|
|
188
|
+
# Email
|
|
189
|
+
EmailError,
|
|
190
|
+
DestinatarioError,
|
|
191
|
+
SmtpError,
|
|
192
|
+
# Validação
|
|
193
|
+
ValidacaoError,
|
|
194
|
+
)
|
|
195
|
+
from nia_etl_utils.ocr import (
|
|
196
|
+
# OCR
|
|
197
|
+
OcrError,
|
|
198
|
+
OcrSubmissaoError,
|
|
199
|
+
OcrProcessamentoError,
|
|
200
|
+
OcrTimeoutError,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Uso em try/except
|
|
204
|
+
try:
|
|
205
|
+
config = PostgresConfig.from_env("_INEXISTENTE")
|
|
206
|
+
except ConfiguracaoError as e:
|
|
207
|
+
logger.error(f"Configuração inválida: {e}")
|
|
208
|
+
logger.debug(f"Detalhes: {e.details}")
|
|
209
|
+
|
|
210
|
+
# Exceções incluem contexto
|
|
211
|
+
try:
|
|
212
|
+
with conectar_postgresql(config) as conn:
|
|
213
|
+
conn.cursor.execute("SELECT * FROM tabela")
|
|
214
|
+
except ConexaoError as e:
|
|
215
|
+
# e.details contém informações adicionais
|
|
216
|
+
print(e.details) # {'host': 'localhost', 'database': 'teste', ...}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### 4. Dataclasses de Resultado (`results.py`)
|
|
222
|
+
|
|
223
|
+
Estruturas para retorno de operações.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from nia_etl_utils import Conexao, ResultadoExtracao, ResultadoLote, ResultadoEmail
|
|
227
|
+
|
|
228
|
+
# Conexao - retornada por conectar_postgresql/oracle
|
|
229
|
+
with conectar_postgresql(config) as conn:
|
|
230
|
+
conn.cursor.execute("SELECT 1")
|
|
231
|
+
# conn.cursor e conn.connection disponíveis
|
|
232
|
+
# Fechamento automático ao sair do context manager
|
|
233
|
+
|
|
234
|
+
# ResultadoExtracao - retornado por extrair_e_exportar_csv
|
|
235
|
+
resultado = extrair_e_exportar_csv(...)
|
|
236
|
+
print(resultado.sucesso) # True/False
|
|
237
|
+
print(resultado.caminho) # '/dados/arquivo.csv'
|
|
238
|
+
print(resultado.linhas) # 1500
|
|
239
|
+
print(resultado.tempo_execucao) # 2.34 (segundos)
|
|
240
|
+
|
|
241
|
+
# ResultadoLote - retornado por exportar_multiplos_csv
|
|
242
|
+
lote = exportar_multiplos_csv(...)
|
|
243
|
+
print(lote.total) # 5
|
|
244
|
+
print(lote.sucessos) # 4
|
|
245
|
+
print(lote.falhas) # 1
|
|
246
|
+
print(lote.resultados) # Lista de ResultadoExtracao
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 5. Conexões de Banco (`database.py`)
|
|
252
|
+
|
|
253
|
+
Conexões com context manager para fechamento automático.
|
|
254
|
+
|
|
255
|
+
#### PostgreSQL
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from nia_etl_utils import conectar_postgresql, PostgresConfig
|
|
259
|
+
|
|
260
|
+
# Com configuração explícita
|
|
261
|
+
config = PostgresConfig(
|
|
262
|
+
host="localhost",
|
|
263
|
+
port="5432",
|
|
264
|
+
database="meu_banco",
|
|
265
|
+
user="usuario",
|
|
266
|
+
password="senha"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
with conectar_postgresql(config) as conn:
|
|
270
|
+
conn.cursor.execute("SELECT * FROM tabela")
|
|
271
|
+
resultados = conn.cursor.fetchall()
|
|
272
|
+
# Conexão fechada automaticamente
|
|
273
|
+
|
|
274
|
+
# Com variáveis de ambiente
|
|
275
|
+
config = PostgresConfig.from_env()
|
|
276
|
+
with conectar_postgresql(config) as conn:
|
|
277
|
+
conn.cursor.execute("SELECT 1")
|
|
278
|
+
|
|
279
|
+
# Wrappers de conveniência (mantidos para retrocompatibilidade)
|
|
280
|
+
from nia_etl_utils import conectar_postgresql_nia, conectar_postgresql_opengeo
|
|
281
|
+
|
|
282
|
+
with conectar_postgresql_nia() as conn:
|
|
283
|
+
conn.cursor.execute("SELECT * FROM ouvidorias")
|
|
284
|
+
|
|
285
|
+
# Engine SQLAlchemy
|
|
286
|
+
from nia_etl_utils import obter_engine_postgresql
|
|
287
|
+
import pandas as pd
|
|
288
|
+
|
|
289
|
+
engine = obter_engine_postgresql(config)
|
|
290
|
+
df = pd.read_sql("SELECT * FROM tabela", engine)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Oracle
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from nia_etl_utils import conectar_oracle, OracleConfig
|
|
297
|
+
|
|
298
|
+
config = OracleConfig.from_env()
|
|
299
|
+
with conectar_oracle(config) as conn:
|
|
300
|
+
conn.cursor.execute("SELECT * FROM tabela WHERE ROWNUM <= 10")
|
|
301
|
+
resultados = conn.cursor.fetchall()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### 6. Email SMTP (`email_smtp.py`)
|
|
307
|
+
|
|
308
|
+
Envio de emails com suporte a anexos.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from nia_etl_utils import enviar_email_smtp
|
|
312
|
+
|
|
313
|
+
# Uso padrão (destinatários da env var EMAIL_DESTINATARIOS)
|
|
314
|
+
enviar_email_smtp(
|
|
315
|
+
corpo_do_email="Pipeline concluído com sucesso",
|
|
316
|
+
assunto="[PROD] ETL Finalizado"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Com destinatários específicos e anexo
|
|
320
|
+
enviar_email_smtp(
|
|
321
|
+
destinatarios=["diretor@mprj.mp.br"],
|
|
322
|
+
corpo_do_email="Relatório executivo anexo",
|
|
323
|
+
assunto="Relatório Mensal",
|
|
324
|
+
anexo="/tmp/relatorio.pdf"
|
|
325
|
+
)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### 7. Logging (`logger_config.py`)
|
|
331
|
+
|
|
332
|
+
Configuração padronizada do Loguru.
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
from nia_etl_utils import configurar_logger_padrao_nia, configurar_logger
|
|
336
|
+
from loguru import logger
|
|
337
|
+
|
|
338
|
+
# Configuração rápida com padrões do NIA
|
|
339
|
+
caminho_log = configurar_logger_padrao_nia("ouvidorias_etl")
|
|
340
|
+
logger.info("Pipeline iniciado")
|
|
341
|
+
|
|
342
|
+
# Configuração customizada
|
|
343
|
+
caminho_log = configurar_logger(
|
|
344
|
+
prefixo="meu_pipeline",
|
|
345
|
+
data_extracao="2025_01_20",
|
|
346
|
+
pasta_logs="/var/logs/nia",
|
|
347
|
+
rotation="50 MB",
|
|
348
|
+
retention="30 days",
|
|
349
|
+
level="INFO"
|
|
350
|
+
)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
### 8. Processamento CSV (`processa_csv.py`)
|
|
356
|
+
|
|
357
|
+
Exportação de DataFrames para CSV com nomenclatura padronizada.
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from nia_etl_utils import exportar_para_csv, extrair_e_exportar_csv
|
|
361
|
+
import pandas as pd
|
|
362
|
+
|
|
363
|
+
# Exportação simples
|
|
364
|
+
df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
|
|
365
|
+
caminho = exportar_para_csv(
|
|
366
|
+
df=df,
|
|
367
|
+
nome_arquivo="dados_clientes",
|
|
368
|
+
data_extracao="2025_01_20",
|
|
369
|
+
diretorio_base="/tmp/dados"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Extração + Exportação
|
|
373
|
+
def extrair_dados():
|
|
374
|
+
return pd.DataFrame({"dados": [1, 2, 3]})
|
|
375
|
+
|
|
376
|
+
resultado = extrair_e_exportar_csv(
|
|
377
|
+
nome_extracao="dados_vendas",
|
|
378
|
+
funcao_extracao=extrair_dados,
|
|
379
|
+
data_extracao="2025_01_20",
|
|
380
|
+
diretorio_base="/tmp/dados",
|
|
381
|
+
falhar_se_vazio=True
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Múltiplas extrações em lote
|
|
385
|
+
from nia_etl_utils import exportar_multiplos_csv
|
|
386
|
+
|
|
387
|
+
extractions = [
|
|
388
|
+
{"nome": "clientes", "funcao": extrair_clientes},
|
|
389
|
+
{"nome": "vendas", "funcao": extrair_vendas}
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
lote = exportar_multiplos_csv(
|
|
393
|
+
extractions=extractions,
|
|
394
|
+
data_extracao="2025_01_20",
|
|
395
|
+
diretorio_base="/tmp/dados"
|
|
396
|
+
)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### 9. Processamento Paralelo de CSV (`processa_csv_paralelo.py`)
|
|
402
|
+
|
|
403
|
+
Processa arquivos CSV grandes em paralelo.
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
from nia_etl_utils import processar_csv_paralelo
|
|
407
|
+
|
|
408
|
+
def limpar_texto(texto):
|
|
409
|
+
return texto.strip().upper()
|
|
410
|
+
|
|
411
|
+
processar_csv_paralelo(
|
|
412
|
+
caminho_entrada="dados_brutos.csv",
|
|
413
|
+
caminho_saida="dados_limpos.csv",
|
|
414
|
+
colunas_para_tratar=["nome", "descricao"],
|
|
415
|
+
funcao_transformacao=limpar_texto,
|
|
416
|
+
remover_entrada=True
|
|
417
|
+
)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
### 10. Manipulação de Arquivos (`limpeza_pastas.py`)
|
|
423
|
+
|
|
424
|
+
Utilitários para limpeza e criação de diretórios.
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from nia_etl_utils import limpar_pasta, remover_pasta_recursivamente, criar_pasta_se_nao_existir
|
|
428
|
+
|
|
429
|
+
limpar_pasta("/tmp/dados")
|
|
430
|
+
remover_pasta_recursivamente("/tmp/temporario")
|
|
431
|
+
criar_pasta_se_nao_existir("/dados/processados/2025/01")
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
### 11. OCR via API IntelliDoc (`ocr.py`)
|
|
437
|
+
|
|
438
|
+
Processamento de OCR (Reconhecimento Óptico de Caracteres) via API IntelliDoc do MPRJ.
|
|
439
|
+
|
|
440
|
+
A API processa documentos de forma assíncrona:
|
|
441
|
+
1. Submete documento → retorna `document_id`
|
|
442
|
+
2. Consulta status via polling → retorna resultado quando pronto
|
|
443
|
+
|
|
444
|
+
**Formatos suportados:** PDF, JPG, PNG, GIF, BMP, TIFF (detecção automática via magic bytes)
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
from nia_etl_utils import executar_ocr, OcrError, OcrTimeoutError
|
|
448
|
+
|
|
449
|
+
# Uso básico com variável de ambiente
|
|
450
|
+
with open("documento.pdf", "rb") as f:
|
|
451
|
+
resultado = executar_ocr(
|
|
452
|
+
conteudo=f.read(),
|
|
453
|
+
url_base="INTELLIDOC_URL", # nome da env var
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
print(resultado["full_text"]) # Texto extraído completo
|
|
457
|
+
print(resultado["overall_quality"]) # Qualidade do OCR (0-1)
|
|
458
|
+
print(resultado["total_pages"]) # Número de páginas
|
|
459
|
+
|
|
460
|
+
# Uso com URL direta e configurações customizadas
|
|
461
|
+
resultado = executar_ocr(
|
|
462
|
+
conteudo=blob_bytes,
|
|
463
|
+
url_base="http://google.com",
|
|
464
|
+
timeout_polling=600, # Timeout máximo em segundos (default: 300)
|
|
465
|
+
max_tentativas=5, # Tentativas de submissão (default: 3)
|
|
466
|
+
intervalo_retry=10, # Segundos entre retries (default: 5)
|
|
467
|
+
intervalo_polling=2, # Segundos entre consultas (default: 1)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# Suporta LOBs do Oracle diretamente
|
|
471
|
+
with conectar_oracle(config) as conn:
|
|
472
|
+
conn.cursor.execute("SELECT blob_documento FROM documentos WHERE id = :id", {"id": 123})
|
|
473
|
+
blob = conn.cursor.fetchone()[0]
|
|
474
|
+
resultado = executar_ocr(blob) # LOB é convertido automaticamente
|
|
475
|
+
|
|
476
|
+
# Acessando detalhes das páginas
|
|
477
|
+
for page in resultado["pages"]:
|
|
478
|
+
print(f"Página {page['page_number']}: {page['extraction_method']}")
|
|
479
|
+
|
|
480
|
+
# Tratamento de erros
|
|
481
|
+
try:
|
|
482
|
+
resultado = executar_ocr(conteudo, url_base="INTELLIDOC_URL")
|
|
483
|
+
except OcrTimeoutError as e:
|
|
484
|
+
logger.error(f"Timeout aguardando OCR: {e}")
|
|
485
|
+
logger.debug(f"Detalhes: {e.details}") # {'document_id': '...', 'ultimo_status': 'PENDING'}
|
|
486
|
+
except OcrError as e:
|
|
487
|
+
logger.error(f"Erro no OCR: {e}")
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Retorno da função `executar_ocr`:**
|
|
491
|
+
|
|
492
|
+
| Campo | Tipo | Descrição |
|
|
493
|
+
| -------------------- | ----- | ----------------------- |
|
|
494
|
+
| `document_id` | str | ID único do documento |
|
|
495
|
+
| `full_text` | str | Texto extraído completo |
|
|
496
|
+
| `mime_type` | str | Tipo MIME detectado |
|
|
497
|
+
| `overall_quality` | float | Qualidade geral (0-1) |
|
|
498
|
+
| `total_pages` | int | Número de páginas |
|
|
499
|
+
| `processing_time_ms` | int | Tempo de processamento |
|
|
500
|
+
| `pages` | list | Detalhes por página |
|
|
501
|
+
| `metadata` | dict | Metadados adicionais |
|
|
502
|
+
|
|
503
|
+
**Exceções:**
|
|
504
|
+
|
|
505
|
+
| Exceção | Quando ocorre |
|
|
506
|
+
| ----------------------- | --------------------------------------------------------------------- |
|
|
507
|
+
| `OcrSubmissaoError` | Falha ao enviar documento (rede, timeout, resposta inválida) |
|
|
508
|
+
| `OcrProcessamentoError` | API retornou FAILURE/REVOKED (documento corrompido, formato inválido) |
|
|
509
|
+
| `OcrTimeoutError` | Tempo máximo de polling atingido |
|
|
510
|
+
| `TypeError` | Tipo de conteúdo não suportado |
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Instalação
|
|
515
|
+
|
|
516
|
+
### Via PyPI
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
pip install nia-etl-utils
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Via GitLab
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
pip install git+https://gitlab-dti.mprj.mp.br/nia/etl-nia/nia-etl-utils.git@v0.2.0
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Modo Desenvolvimento
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
git clone https://gitlab-dti.mprj.mp.br/nia/etl-nia/nia-etl-utils.git
|
|
532
|
+
cd nia-etl-utils
|
|
533
|
+
pip install -e ".[dev]"
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## Configuração
|
|
539
|
+
|
|
540
|
+
### Variáveis de Ambiente
|
|
541
|
+
|
|
542
|
+
```env
|
|
543
|
+
# Email SMTP
|
|
544
|
+
MAIL_SMTP_SERVER=smtp.mprj.mp.br
|
|
545
|
+
MAIL_SMTP_PORT=587
|
|
546
|
+
MAIL_SENDER=etl@mprj.mp.br
|
|
547
|
+
EMAIL_DESTINATARIOS=equipe@mprj.mp.br,gestor@mprj.mp.br
|
|
548
|
+
|
|
549
|
+
# PostgreSQL - NIA
|
|
550
|
+
DB_POSTGRESQL_HOST=postgres-nia.mprj.mp.br
|
|
551
|
+
DB_POSTGRESQL_PORT=5432
|
|
552
|
+
DB_POSTGRESQL_DATABASE=nia_database
|
|
553
|
+
DB_POSTGRESQL_USER=usuario
|
|
554
|
+
DB_POSTGRESQL_PASSWORD=senha
|
|
555
|
+
|
|
556
|
+
# PostgreSQL - OpenGeo
|
|
557
|
+
DB_POSTGRESQL_HOST_OPENGEO=postgres-opengeo.mprj.mp.br
|
|
558
|
+
DB_POSTGRESQL_PORT_OPENGEO=5432
|
|
559
|
+
DB_POSTGRESQL_DATABASE_OPENGEO=opengeo_database
|
|
560
|
+
DB_POSTGRESQL_USER_OPENGEO=usuario
|
|
561
|
+
DB_POSTGRESQL_PASSWORD_OPENGEO=senha
|
|
562
|
+
|
|
563
|
+
# Oracle
|
|
564
|
+
DB_ORACLE_HOST=oracle.mprj.mp.br
|
|
565
|
+
DB_ORACLE_PORT=1521
|
|
566
|
+
DB_ORACLE_SERVICE_NAME=ORCL
|
|
567
|
+
DB_ORACLE_USER=usuario
|
|
568
|
+
DB_ORACLE_PASSWORD=senha
|
|
569
|
+
|
|
570
|
+
# OCR (IntelliDoc)
|
|
571
|
+
INTELLIDOC_URL=http://intellidoc.mprj.mp.br
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Testes
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
# Todos os testes
|
|
580
|
+
pytest
|
|
581
|
+
|
|
582
|
+
# Com cobertura
|
|
583
|
+
pytest --cov=src/nia_etl_utils --cov-report=term-missing
|
|
584
|
+
|
|
585
|
+
# Ou usar o script helper
|
|
586
|
+
./run_tests.sh --coverage --verbose
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Exemplo de Uso Completo
|
|
592
|
+
|
|
593
|
+
```python
|
|
594
|
+
from nia_etl_utils import (
|
|
595
|
+
configurar_logger_padrao_nia,
|
|
596
|
+
PostgresConfig,
|
|
597
|
+
conectar_postgresql,
|
|
598
|
+
exportar_para_csv,
|
|
599
|
+
ConexaoError,
|
|
600
|
+
)
|
|
601
|
+
from loguru import logger
|
|
602
|
+
import pandas as pd
|
|
603
|
+
from datetime import datetime
|
|
604
|
+
|
|
605
|
+
# 1. Configura logging
|
|
606
|
+
configurar_logger_padrao_nia("meu_pipeline")
|
|
607
|
+
|
|
608
|
+
# 2. Carrega configuração
|
|
609
|
+
config = PostgresConfig.from_env()
|
|
610
|
+
|
|
611
|
+
# 3. Conecta e extrai dados
|
|
612
|
+
try:
|
|
613
|
+
with conectar_postgresql(config) as conn:
|
|
614
|
+
logger.info("Extraindo dados...")
|
|
615
|
+
conn.cursor.execute("SELECT * FROM tabela WHERE data >= CURRENT_DATE - 7")
|
|
616
|
+
resultados = conn.cursor.fetchall()
|
|
617
|
+
colunas = [desc[0] for desc in conn.cursor.description]
|
|
618
|
+
df = pd.DataFrame(resultados, columns=colunas)
|
|
619
|
+
except ConexaoError as e:
|
|
620
|
+
logger.error(f"Falha na conexão: {e}")
|
|
621
|
+
raise
|
|
622
|
+
|
|
623
|
+
logger.info(f"Extração concluída: {len(df)} registros")
|
|
624
|
+
|
|
625
|
+
# 4. Exporta CSV
|
|
626
|
+
data_hoje = datetime.now().strftime("%Y_%m_%d")
|
|
627
|
+
caminho = exportar_para_csv(
|
|
628
|
+
df=df,
|
|
629
|
+
nome_arquivo="dados_extraidos",
|
|
630
|
+
data_extracao=data_hoje,
|
|
631
|
+
diretorio_base="/dados/processados"
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
logger.success(f"Pipeline concluído! Arquivo: {caminho}")
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## Integração com Airflow
|
|
640
|
+
|
|
641
|
+
```python
|
|
642
|
+
from airflow.providers.cncf.kubernetes.operators.kubernetes_pod import KubernetesPodOperator
|
|
643
|
+
|
|
644
|
+
task = KubernetesPodOperator(
|
|
645
|
+
task_id="meu_etl",
|
|
646
|
+
name="meu-etl-pod",
|
|
647
|
+
namespace="airflow-nia-stage",
|
|
648
|
+
image="python:3.13.3",
|
|
649
|
+
cmds=[
|
|
650
|
+
"sh", "-c",
|
|
651
|
+
"pip install nia-etl-utils && python src/extract.py"
|
|
652
|
+
],
|
|
653
|
+
env_vars={
|
|
654
|
+
"DB_POSTGRESQL_HOST": "...",
|
|
655
|
+
"EMAIL_DESTINATARIOS": "equipe@mprj.mp.br"
|
|
656
|
+
},
|
|
657
|
+
)
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## Tecnologias Utilizadas
|
|
663
|
+
|
|
664
|
+
- Python 3.13.3
|
|
665
|
+
- Loguru (logging)
|
|
666
|
+
- python-dotenv (env vars)
|
|
667
|
+
- requests (HTTP/OCR)
|
|
668
|
+
- cx_Oracle (Oracle)
|
|
669
|
+
- psycopg2 (PostgreSQL)
|
|
670
|
+
- SQLAlchemy (engines)
|
|
671
|
+
- pandas (processamento de dados)
|
|
672
|
+
- pytest + pytest-cov (testes)
|
|
673
|
+
- ruff (linting)
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## Versionamento
|
|
678
|
+
|
|
679
|
+
Este projeto usa [Semantic Versioning](https://semver.org/):
|
|
680
|
+
|
|
681
|
+
- **MAJOR**: Mudanças incompatíveis na API
|
|
682
|
+
- **MINOR**: Novas funcionalidades (retrocompatíveis)
|
|
683
|
+
- **PATCH**: Correções de bugs
|
|
684
|
+
|
|
685
|
+
**Versão atual:** `v0.2.2`
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
689
|
+
## CI/CD
|
|
690
|
+
|
|
691
|
+
Pipeline automatizado no GitLab:
|
|
692
|
+
|
|
693
|
+
- Testes unitários (pytest)
|
|
694
|
+
- Cobertura de código (>= 70%)
|
|
695
|
+
- Linting (ruff)
|
|
696
|
+
- Deploy automático no PyPI (em tags)
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## Contribuição
|
|
701
|
+
|
|
702
|
+
Merge requests são bem-vindos. Sempre crie uma branch a partir de `main`.
|
|
703
|
+
|
|
704
|
+
### Checklist:
|
|
705
|
+
|
|
706
|
+
- [ ] Testes passam: `pytest`
|
|
707
|
+
- [ ] Cobertura >= 70%
|
|
708
|
+
- [ ] Lint OK: `ruff check src/ tests/`
|
|
709
|
+
- [ ] Commits semânticos
|
|
710
|
+
- [ ] Documentação atualizada
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Licença
|
|
715
|
+
|
|
716
|
+
Projeto de uso interno do MPRJ. Sem licença pública.
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Responsável Técnico
|
|
721
|
+
|
|
722
|
+
**Nícolas Galdino Esmael** | Engenheiro de Dados - NIA | MPRJ
|