dataloom-engine 0.3.0__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.
- dataloom_engine-0.3.0/LICENSE +21 -0
- dataloom_engine-0.3.0/PKG-INFO +196 -0
- dataloom_engine-0.3.0/README.md +164 -0
- dataloom_engine-0.3.0/dataloom_engine/__init__.py +43 -0
- dataloom_engine-0.3.0/dataloom_engine/config.py +52 -0
- dataloom_engine-0.3.0/dataloom_engine/exceptions.py +25 -0
- dataloom_engine-0.3.0/dataloom_engine/hooks.py +50 -0
- dataloom_engine-0.3.0/dataloom_engine/logs.py +28 -0
- dataloom_engine-0.3.0/dataloom_engine/loom.py +167 -0
- dataloom_engine-0.3.0/dataloom_engine/processors.py +43 -0
- dataloom_engine-0.3.0/dataloom_engine/py.typed +0 -0
- dataloom_engine-0.3.0/dataloom_engine/sinks.py +172 -0
- dataloom_engine-0.3.0/dataloom_engine/sources.py +56 -0
- dataloom_engine-0.3.0/dataloom_engine/types.py +18 -0
- dataloom_engine-0.3.0/dataloom_engine/weaver.py +84 -0
- dataloom_engine-0.3.0/dataloom_engine.egg-info/PKG-INFO +196 -0
- dataloom_engine-0.3.0/dataloom_engine.egg-info/SOURCES.txt +24 -0
- dataloom_engine-0.3.0/dataloom_engine.egg-info/dependency_links.txt +1 -0
- dataloom_engine-0.3.0/dataloom_engine.egg-info/requires.txt +7 -0
- dataloom_engine-0.3.0/dataloom_engine.egg-info/top_level.txt +1 -0
- dataloom_engine-0.3.0/pyproject.toml +80 -0
- dataloom_engine-0.3.0/setup.cfg +4 -0
- dataloom_engine-0.3.0/tests/test_config.py +45 -0
- dataloom_engine-0.3.0/tests/test_core.py +162 -0
- dataloom_engine-0.3.0/tests/test_loom.py +318 -0
- dataloom_engine-0.3.0/tests/test_sinks.py +181 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 [Dioni Padilha]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataloom-engine
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: DataLoom: Um orquestrador de threads leve e eficiente para dados.
|
|
5
|
+
Author-email: Dioni Padilha <dionipdl@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dionipadilha/dataloom
|
|
8
|
+
Project-URL: Changelog, https://github.com/dionipadilha/dataloom/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/dionipadilha/dataloom/issues
|
|
10
|
+
Keywords: pipeline,data,threading,loom,concurrency
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
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 :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.21.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# 🧵 DataLoom
|
|
34
|
+
|
|
35
|
+
> Um motor de orquestração multi-thread leve, eficiente e seguro para Python.
|
|
36
|
+
|
|
37
|
+
[](https://github.com/dionipadilha/dataloom/actions/workflows/ci.yml)  
|
|
38
|
+
|
|
39
|
+
<img width="2752" height="1536" alt="DataLoom: Um motor de orquestração multi-thread leve, eficiente e seguro para Python." src="https://github.com/user-attachments/assets/c1f3fc98-5eaf-4add-a38c-2717ae699cd5" />
|
|
40
|
+
|
|
41
|
+
O **DataLoom** é uma biblioteca projetada para processar fluxos de dados utilizando o padrão **Produtor-Consumidor** com múltiplas threads (Weavers). Ele abstrai a complexidade de filas (`Queues`), sincronização (`Locks`) e gerenciamento de ciclo de vida, permitindo que você foque apenas na lógica de transformação dos dados.
|
|
42
|
+
|
|
43
|
+
## Por que usar o DataLoom?
|
|
44
|
+
⚡ Performance: Processamento paralelo real para tarefas I/O bound.
|
|
45
|
+
🧩 Simplicidade: API intuitiva inspirada na metáfora de tecelagem.
|
|
46
|
+
🛡️ Segurança: Thread-safety garantido por design em todo o pipeline.
|
|
47
|
+
📦 Leve: Dependências mínimas, pronto para rodar em qualquer ambiente Python.
|
|
48
|
+
|
|
49
|
+
### Por que não usar só `ThreadPoolExecutor`?
|
|
50
|
+
|
|
51
|
+
Pergunta justa — a stdlib resolve o *paralelismo*, mas não o *pipeline*. O
|
|
52
|
+
`concurrent.futures` te dá um pool de threads; tudo ao redor fica por sua conta:
|
|
53
|
+
|
|
54
|
+
| Você precisa de... | Com a stdlib | Com o DataLoom |
|
|
55
|
+
| --------------------------------------------------- | ------------------------ | --------------------------------- |
|
|
56
|
+
| Fluxo contínuo produtor-consumidor | `Queue` + loops manuais | `Loom` + `Source` |
|
|
57
|
+
| Backpressure (produtor mais rápido que consumidores) | `Queue(maxsize=...)` manual | Padrão, configurável |
|
|
58
|
+
| Shutdown limpo (drenar fila, fechar recursos) | Sentinelas e joins manuais | `stop()` / `with Loom(...)` |
|
|
59
|
+
| Worker que sobrevive a erros e os reporta | try/except em cada worker | `hooks.on_error` centralizado |
|
|
60
|
+
| Métricas por item processado | Instrumentação manual | `hooks.on_batch_processed` |
|
|
61
|
+
| Escrita concorrente segura em arquivo | Lock manual | Sinks prontos (JSON, CSV, callback) |
|
|
62
|
+
|
|
63
|
+
Se o seu caso é "aplicar uma função a uma lista e coletar os resultados",
|
|
64
|
+
use `ThreadPoolExecutor.map` — é a ferramenta certa. O DataLoom é para
|
|
65
|
+
**fluxos contínuos ou longos** onde ciclo de vida, resiliência e
|
|
66
|
+
observabilidade importam. E, como toda solução baseada em threads no
|
|
67
|
+
CPython, o ganho de paralelismo vale para cargas **I/O bound**; para
|
|
68
|
+
CPU bound, prefira multiprocessing.
|
|
69
|
+
|
|
70
|
+
## ✨ Características
|
|
71
|
+
|
|
72
|
+
- **API Coesa:** Conceitos alinhados (`Loom`, `Weaver`, `Sink`).
|
|
73
|
+
- **Concorrente:** Gerencia automaticamente múltiplos _Weavers_ (threads) em paralelo.
|
|
74
|
+
- **Seguro:** Sinks padrão são thread-safe e exceções são tipadas (`LoomError`).
|
|
75
|
+
- **Resiliente:** Erros de processamento não derrubam os Weavers e são reportados via hooks.
|
|
76
|
+
- **Gerenciável:** `Loom` é um context manager — `with Loom(...) as loom:` garante `stop()` automático.
|
|
77
|
+
- **Backpressure:** Fila de tarefas limitada por padrão (`queue_maxsize`), evitando crescimento de memória sem controle.
|
|
78
|
+
- **Observável:** Hooks de ciclo de vida e métricas por lote (`on_batch_processed` com resultado e duração), além de Logs integrados.
|
|
79
|
+
|
|
80
|
+
## 📦 Instalação
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install dataloom-engine
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> ⚠️ **Atenção ao nome:** o pacote instala o módulo `dataloom_engine`
|
|
87
|
+
> (`import dataloom_engine`). Não confunda com o pacote `dataloom` do PyPI,
|
|
88
|
+
> que é um ORM de outro autor e não tem relação com este projeto.
|
|
89
|
+
|
|
90
|
+
Para desenvolver ou usar a versão mais recente do repositório:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/dionipadilha/dataloom.git
|
|
94
|
+
cd dataloom
|
|
95
|
+
pip install -e .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 🚀 Uso Rápido
|
|
99
|
+
|
|
100
|
+
Aqui está um exemplo completo de como tecer um pipeline de dados:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from pathlib import Path
|
|
104
|
+
import numpy as np
|
|
105
|
+
from dataloom_engine import (
|
|
106
|
+
Loom,
|
|
107
|
+
LoomConfig,
|
|
108
|
+
LoomLogs,
|
|
109
|
+
Processor,
|
|
110
|
+
JsonFileSink,
|
|
111
|
+
ThreadedBufferedSink
|
|
112
|
+
)
|
|
113
|
+
from dataloom_engine.sources import RandomNumPySource
|
|
114
|
+
|
|
115
|
+
# 1. Defina sua lógica de processamento (Stateless)
|
|
116
|
+
class MyFilterProcessor(Processor):
|
|
117
|
+
def process(self, batch: np.ndarray) -> dict:
|
|
118
|
+
# 'batch' é um array numpy com o tamanho definido na config
|
|
119
|
+
avg = float(batch.mean())
|
|
120
|
+
return {
|
|
121
|
+
"processed_items": len(batch),
|
|
122
|
+
"average_value": avg,
|
|
123
|
+
"status": "high" if avg > 0.5 else "low"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 2. Configuração Inicial
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
# Configura logs no console
|
|
129
|
+
LoomLogs.setup()
|
|
130
|
+
|
|
131
|
+
# Define parâmetros do motor
|
|
132
|
+
config = LoomConfig(
|
|
133
|
+
output_dir=Path("./data_out"),
|
|
134
|
+
batch_size=100, # Processa 100 itens por vez
|
|
135
|
+
interval_seconds=1 # Gera um novo lote a cada 1 segundo
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# 3. Fonte de Dados
|
|
139
|
+
# Agora desvinculada do motor, permitindo fontes customizadas (DB, CSV, S3)
|
|
140
|
+
source = RandomNumPySource(config)
|
|
141
|
+
|
|
142
|
+
# 4. Sink Arquivado e Otimizado
|
|
143
|
+
# ThreadedBufferedSink evita que o I/O bloqueie o processamento
|
|
144
|
+
file_sink = JsonFileSink(config.output_dir)
|
|
145
|
+
sink = ThreadedBufferedSink(file_sink)
|
|
146
|
+
|
|
147
|
+
# 5. Inicializa o Loom (O Orquestrador)
|
|
148
|
+
loom = Loom(
|
|
149
|
+
config=config,
|
|
150
|
+
processor=MyFilterProcessor(),
|
|
151
|
+
sink=sink,
|
|
152
|
+
source=source,
|
|
153
|
+
num_weavers=4 # 4 Threads trabalhando em paralelo
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
print("🧵 DataLoom iniciado! Pressione Ctrl+C para parar.")
|
|
157
|
+
try:
|
|
158
|
+
# O context manager garante stop() e limpeza dos recursos,
|
|
159
|
+
# mesmo em caso de exceção ou Ctrl+C
|
|
160
|
+
with loom:
|
|
161
|
+
loom.start()
|
|
162
|
+
except KeyboardInterrupt:
|
|
163
|
+
print("\n🛑 Tear parado.")
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 🏗️ Arquitetura
|
|
167
|
+
|
|
168
|
+
O DataLoom utiliza uma metáfora de tecelagem:
|
|
169
|
+
|
|
170
|
+
- **Loom (Tear):** A máquina principal. Gerencia a fila de tarefas e o ciclo de vida.
|
|
171
|
+
- **Weaver (Tecelão):** As threads trabalhadoras. Elas pegam a matéria-prima (batch), processam e entregam.
|
|
172
|
+
- **Processor:** A lógica de negócio. Transforma dados brutos em informação.
|
|
173
|
+
- **Sink:** O destino final. Onde o produto acabado é depositado (ex: `JsonFileSink`, `CsvFileSink`, ou qualquer destino via `CallbackSink`).
|
|
174
|
+
|
|
175
|
+
## 🛠️ Desenvolvimento e Testes
|
|
176
|
+
|
|
177
|
+
Para contribuir com o projeto ou rodar os testes unitários:
|
|
178
|
+
|
|
179
|
+
1. Instale as dependências de desenvolvimento:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install -e ".[dev]"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
2. Rode a suíte de testes (via pytest):
|
|
186
|
+
```bash
|
|
187
|
+
pytest
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 📄 Licença
|
|
191
|
+
|
|
192
|
+
Este projeto está licenciado sob a licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
|
193
|
+
|
|
194
|
+
## 🗒️ Histórico de versões
|
|
195
|
+
|
|
196
|
+
As mudanças de cada versão estão documentadas no [CHANGELOG](CHANGELOG.md).
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# 🧵 DataLoom
|
|
2
|
+
|
|
3
|
+
> Um motor de orquestração multi-thread leve, eficiente e seguro para Python.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/dionipadilha/dataloom/actions/workflows/ci.yml)  
|
|
6
|
+
|
|
7
|
+
<img width="2752" height="1536" alt="DataLoom: Um motor de orquestração multi-thread leve, eficiente e seguro para Python." src="https://github.com/user-attachments/assets/c1f3fc98-5eaf-4add-a38c-2717ae699cd5" />
|
|
8
|
+
|
|
9
|
+
O **DataLoom** é uma biblioteca projetada para processar fluxos de dados utilizando o padrão **Produtor-Consumidor** com múltiplas threads (Weavers). Ele abstrai a complexidade de filas (`Queues`), sincronização (`Locks`) e gerenciamento de ciclo de vida, permitindo que você foque apenas na lógica de transformação dos dados.
|
|
10
|
+
|
|
11
|
+
## Por que usar o DataLoom?
|
|
12
|
+
⚡ Performance: Processamento paralelo real para tarefas I/O bound.
|
|
13
|
+
🧩 Simplicidade: API intuitiva inspirada na metáfora de tecelagem.
|
|
14
|
+
🛡️ Segurança: Thread-safety garantido por design em todo o pipeline.
|
|
15
|
+
📦 Leve: Dependências mínimas, pronto para rodar em qualquer ambiente Python.
|
|
16
|
+
|
|
17
|
+
### Por que não usar só `ThreadPoolExecutor`?
|
|
18
|
+
|
|
19
|
+
Pergunta justa — a stdlib resolve o *paralelismo*, mas não o *pipeline*. O
|
|
20
|
+
`concurrent.futures` te dá um pool de threads; tudo ao redor fica por sua conta:
|
|
21
|
+
|
|
22
|
+
| Você precisa de... | Com a stdlib | Com o DataLoom |
|
|
23
|
+
| --------------------------------------------------- | ------------------------ | --------------------------------- |
|
|
24
|
+
| Fluxo contínuo produtor-consumidor | `Queue` + loops manuais | `Loom` + `Source` |
|
|
25
|
+
| Backpressure (produtor mais rápido que consumidores) | `Queue(maxsize=...)` manual | Padrão, configurável |
|
|
26
|
+
| Shutdown limpo (drenar fila, fechar recursos) | Sentinelas e joins manuais | `stop()` / `with Loom(...)` |
|
|
27
|
+
| Worker que sobrevive a erros e os reporta | try/except em cada worker | `hooks.on_error` centralizado |
|
|
28
|
+
| Métricas por item processado | Instrumentação manual | `hooks.on_batch_processed` |
|
|
29
|
+
| Escrita concorrente segura em arquivo | Lock manual | Sinks prontos (JSON, CSV, callback) |
|
|
30
|
+
|
|
31
|
+
Se o seu caso é "aplicar uma função a uma lista e coletar os resultados",
|
|
32
|
+
use `ThreadPoolExecutor.map` — é a ferramenta certa. O DataLoom é para
|
|
33
|
+
**fluxos contínuos ou longos** onde ciclo de vida, resiliência e
|
|
34
|
+
observabilidade importam. E, como toda solução baseada em threads no
|
|
35
|
+
CPython, o ganho de paralelismo vale para cargas **I/O bound**; para
|
|
36
|
+
CPU bound, prefira multiprocessing.
|
|
37
|
+
|
|
38
|
+
## ✨ Características
|
|
39
|
+
|
|
40
|
+
- **API Coesa:** Conceitos alinhados (`Loom`, `Weaver`, `Sink`).
|
|
41
|
+
- **Concorrente:** Gerencia automaticamente múltiplos _Weavers_ (threads) em paralelo.
|
|
42
|
+
- **Seguro:** Sinks padrão são thread-safe e exceções são tipadas (`LoomError`).
|
|
43
|
+
- **Resiliente:** Erros de processamento não derrubam os Weavers e são reportados via hooks.
|
|
44
|
+
- **Gerenciável:** `Loom` é um context manager — `with Loom(...) as loom:` garante `stop()` automático.
|
|
45
|
+
- **Backpressure:** Fila de tarefas limitada por padrão (`queue_maxsize`), evitando crescimento de memória sem controle.
|
|
46
|
+
- **Observável:** Hooks de ciclo de vida e métricas por lote (`on_batch_processed` com resultado e duração), além de Logs integrados.
|
|
47
|
+
|
|
48
|
+
## 📦 Instalação
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install dataloom-engine
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> ⚠️ **Atenção ao nome:** o pacote instala o módulo `dataloom_engine`
|
|
55
|
+
> (`import dataloom_engine`). Não confunda com o pacote `dataloom` do PyPI,
|
|
56
|
+
> que é um ORM de outro autor e não tem relação com este projeto.
|
|
57
|
+
|
|
58
|
+
Para desenvolver ou usar a versão mais recente do repositório:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/dionipadilha/dataloom.git
|
|
62
|
+
cd dataloom
|
|
63
|
+
pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 🚀 Uso Rápido
|
|
67
|
+
|
|
68
|
+
Aqui está um exemplo completo de como tecer um pipeline de dados:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from pathlib import Path
|
|
72
|
+
import numpy as np
|
|
73
|
+
from dataloom_engine import (
|
|
74
|
+
Loom,
|
|
75
|
+
LoomConfig,
|
|
76
|
+
LoomLogs,
|
|
77
|
+
Processor,
|
|
78
|
+
JsonFileSink,
|
|
79
|
+
ThreadedBufferedSink
|
|
80
|
+
)
|
|
81
|
+
from dataloom_engine.sources import RandomNumPySource
|
|
82
|
+
|
|
83
|
+
# 1. Defina sua lógica de processamento (Stateless)
|
|
84
|
+
class MyFilterProcessor(Processor):
|
|
85
|
+
def process(self, batch: np.ndarray) -> dict:
|
|
86
|
+
# 'batch' é um array numpy com o tamanho definido na config
|
|
87
|
+
avg = float(batch.mean())
|
|
88
|
+
return {
|
|
89
|
+
"processed_items": len(batch),
|
|
90
|
+
"average_value": avg,
|
|
91
|
+
"status": "high" if avg > 0.5 else "low"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# 2. Configuração Inicial
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
# Configura logs no console
|
|
97
|
+
LoomLogs.setup()
|
|
98
|
+
|
|
99
|
+
# Define parâmetros do motor
|
|
100
|
+
config = LoomConfig(
|
|
101
|
+
output_dir=Path("./data_out"),
|
|
102
|
+
batch_size=100, # Processa 100 itens por vez
|
|
103
|
+
interval_seconds=1 # Gera um novo lote a cada 1 segundo
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 3. Fonte de Dados
|
|
107
|
+
# Agora desvinculada do motor, permitindo fontes customizadas (DB, CSV, S3)
|
|
108
|
+
source = RandomNumPySource(config)
|
|
109
|
+
|
|
110
|
+
# 4. Sink Arquivado e Otimizado
|
|
111
|
+
# ThreadedBufferedSink evita que o I/O bloqueie o processamento
|
|
112
|
+
file_sink = JsonFileSink(config.output_dir)
|
|
113
|
+
sink = ThreadedBufferedSink(file_sink)
|
|
114
|
+
|
|
115
|
+
# 5. Inicializa o Loom (O Orquestrador)
|
|
116
|
+
loom = Loom(
|
|
117
|
+
config=config,
|
|
118
|
+
processor=MyFilterProcessor(),
|
|
119
|
+
sink=sink,
|
|
120
|
+
source=source,
|
|
121
|
+
num_weavers=4 # 4 Threads trabalhando em paralelo
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
print("🧵 DataLoom iniciado! Pressione Ctrl+C para parar.")
|
|
125
|
+
try:
|
|
126
|
+
# O context manager garante stop() e limpeza dos recursos,
|
|
127
|
+
# mesmo em caso de exceção ou Ctrl+C
|
|
128
|
+
with loom:
|
|
129
|
+
loom.start()
|
|
130
|
+
except KeyboardInterrupt:
|
|
131
|
+
print("\n🛑 Tear parado.")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 🏗️ Arquitetura
|
|
135
|
+
|
|
136
|
+
O DataLoom utiliza uma metáfora de tecelagem:
|
|
137
|
+
|
|
138
|
+
- **Loom (Tear):** A máquina principal. Gerencia a fila de tarefas e o ciclo de vida.
|
|
139
|
+
- **Weaver (Tecelão):** As threads trabalhadoras. Elas pegam a matéria-prima (batch), processam e entregam.
|
|
140
|
+
- **Processor:** A lógica de negócio. Transforma dados brutos em informação.
|
|
141
|
+
- **Sink:** O destino final. Onde o produto acabado é depositado (ex: `JsonFileSink`, `CsvFileSink`, ou qualquer destino via `CallbackSink`).
|
|
142
|
+
|
|
143
|
+
## 🛠️ Desenvolvimento e Testes
|
|
144
|
+
|
|
145
|
+
Para contribuir com o projeto ou rodar os testes unitários:
|
|
146
|
+
|
|
147
|
+
1. Instale as dependências de desenvolvimento:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install -e ".[dev]"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. Rode a suíte de testes (via pytest):
|
|
154
|
+
```bash
|
|
155
|
+
pytest
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 📄 Licença
|
|
159
|
+
|
|
160
|
+
Este projeto está licenciado sob a licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
|
161
|
+
|
|
162
|
+
## 🗒️ Histórico de versões
|
|
163
|
+
|
|
164
|
+
As mudanças de cada versão estão documentadas no [CHANGELOG](CHANGELOG.md).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# dataloom_engine/__init__.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
DataLoom: Um motor de orquestração multi-thread leve e eficiente.
|
|
5
|
+
|
|
6
|
+
Este módulo expõe a API pública do pacote. Observe que classes internas
|
|
7
|
+
como 'Weaver' não são expostas propositalmente, mantendo a superfície
|
|
8
|
+
de uso limpa e segura para o consumidor.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataloom_engine.config import LoomConfig
|
|
12
|
+
from dataloom_engine.exceptions import ConfigurationError, LoomError, WeaverError
|
|
13
|
+
from dataloom_engine.hooks import LoomHooks
|
|
14
|
+
from dataloom_engine.logs import LoomLogs
|
|
15
|
+
from dataloom_engine.loom import Loom
|
|
16
|
+
from dataloom_engine.processors import Processor
|
|
17
|
+
from dataloom_engine.sinks import (
|
|
18
|
+
CallbackSink,
|
|
19
|
+
CsvFileSink,
|
|
20
|
+
JsonFileSink,
|
|
21
|
+
Sink,
|
|
22
|
+
ThreadedBufferedSink,
|
|
23
|
+
)
|
|
24
|
+
from dataloom_engine.sources import Source
|
|
25
|
+
from dataloom_engine.types import LoomState
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Loom",
|
|
29
|
+
"LoomConfig",
|
|
30
|
+
"LoomState",
|
|
31
|
+
"Processor",
|
|
32
|
+
"Sink",
|
|
33
|
+
"JsonFileSink",
|
|
34
|
+
"CsvFileSink",
|
|
35
|
+
"CallbackSink",
|
|
36
|
+
"ThreadedBufferedSink",
|
|
37
|
+
"Source",
|
|
38
|
+
"LoomHooks",
|
|
39
|
+
"LoomLogs",
|
|
40
|
+
"LoomError",
|
|
41
|
+
"WeaverError",
|
|
42
|
+
"ConfigurationError",
|
|
43
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# dataloom_engine/config.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Gerenciamento de configuração do orquestrador.
|
|
5
|
+
Centraliza parâmetros operacionais como diretórios e tamanhos de lote.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Union
|
|
11
|
+
|
|
12
|
+
from dataloom_engine.exceptions import ConfigurationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class LoomConfig:
|
|
17
|
+
"""
|
|
18
|
+
Objeto de configuração principal do DataLoom.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
output_dir (Path): Diretório base onde os Sinks padrão salvarão dados.
|
|
22
|
+
Strings são convertidas para Path automaticamente.
|
|
23
|
+
batch_size (int): Quantidade de itens gerados por ciclo de processamento.
|
|
24
|
+
interval_seconds (float): Intervalo de tempo entre gerações de tarefas.
|
|
25
|
+
queue_maxsize (Optional[int]): Capacidade máxima da fila de tarefas
|
|
26
|
+
(backpressure). None usa o padrão do Loom (num_weavers * 4);
|
|
27
|
+
0 significa fila ilimitada.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ConfigurationError: se algum parâmetro for inválido.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
output_dir: Union[str, Path]
|
|
34
|
+
batch_size: int = 10
|
|
35
|
+
interval_seconds: float = 1.0
|
|
36
|
+
queue_maxsize: Optional[int] = None
|
|
37
|
+
|
|
38
|
+
def __post_init__(self) -> None:
|
|
39
|
+
self.output_dir = Path(self.output_dir)
|
|
40
|
+
|
|
41
|
+
if self.batch_size <= 0:
|
|
42
|
+
raise ConfigurationError(
|
|
43
|
+
f"batch_size deve ser maior que zero (recebido: {self.batch_size})."
|
|
44
|
+
)
|
|
45
|
+
if self.interval_seconds < 0:
|
|
46
|
+
raise ConfigurationError(
|
|
47
|
+
f"interval_seconds não pode ser negativo (recebido: {self.interval_seconds})."
|
|
48
|
+
)
|
|
49
|
+
if self.queue_maxsize is not None and self.queue_maxsize < 0:
|
|
50
|
+
raise ConfigurationError(
|
|
51
|
+
f"queue_maxsize não pode ser negativo (recebido: {self.queue_maxsize})."
|
|
52
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# dataloom_engine/exceptions.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Exceções personalizadas para o DataLoom.
|
|
5
|
+
Permite que consumidores capturem erros específicos da biblioteca
|
|
6
|
+
sem depender de exceções genéricas do Python.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoomError(Exception):
|
|
11
|
+
"""Exceção base para todos os erros do DataLoom."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigurationError(LoomError):
|
|
17
|
+
"""Lançado quando há problemas na validação do LoomConfig."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WeaverError(LoomError):
|
|
23
|
+
"""Lançado quando um Weaver falha de forma irrecuperável."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# dataloom_engine/hooks.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Pontos de extensão para observabilidade (Hooks).
|
|
5
|
+
Permite injetar lógica de monitoramento sem acoplar ao core do engine.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoomHooks:
|
|
12
|
+
"""
|
|
13
|
+
Classe base para hooks do ciclo de vida.
|
|
14
|
+
Todos os métodos são opcionais e no-op por padrão: sobrescreva apenas
|
|
15
|
+
os pontos de interesse. (Deliberadamente não é um ABC — não há método
|
|
16
|
+
obrigatório a implementar.)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def on_start(self) -> None:
|
|
20
|
+
"""Chamado imediatamente antes dos Weavers serem iniciados."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def on_stop(self) -> None:
|
|
24
|
+
"""Chamado após o encerramento gracioso do orquestrador."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def on_error(self, error: Exception) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Chamado quando ocorre uma exceção no loop principal do Loom ou
|
|
30
|
+
durante o processamento de um lote em um Weaver (WeaverError).
|
|
31
|
+
|
|
32
|
+
Atenção: pode ser invocado a partir de múltiplas threads Weaver
|
|
33
|
+
simultaneamente — implementações devem ser thread-safe.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def on_batch_processed(self, result: Dict[str, Any], duration_seconds: float) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Chamado após cada lote processado e entregue ao Sink com sucesso.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
result: O dicionário produzido pelo Processor para o lote.
|
|
43
|
+
duration_seconds: Tempo gasto em process() + send(), medido
|
|
44
|
+
com relógio monotônico.
|
|
45
|
+
|
|
46
|
+
Atenção: assim como on_error, é invocado a partir das threads
|
|
47
|
+
Weaver — implementações devem ser thread-safe e rápidas, pois
|
|
48
|
+
rodam no caminho quente do pipeline.
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# dataloom_engine/logs.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Utilitários de configuração de logging para o DataLoom.
|
|
5
|
+
Fornece um namespace estático para configuração rápida.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoomLogs:
|
|
12
|
+
"""
|
|
13
|
+
Namespace utilitário para gerenciamento de logs do DataLoom.
|
|
14
|
+
Não deve ser instanciado, apenas usado estaticamente.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def setup(level: int = logging.INFO) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Configura o logging básico para stdout.
|
|
21
|
+
|
|
22
|
+
Uso:
|
|
23
|
+
LoomLogs.setup(level=logging.DEBUG)
|
|
24
|
+
"""
|
|
25
|
+
logging.basicConfig(
|
|
26
|
+
level=level,
|
|
27
|
+
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
28
|
+
)
|