dataloom-engine 0.3.0__tar.gz → 0.4.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.4.0/PKG-INFO +214 -0
- dataloom_engine-0.4.0/README.md +180 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/__init__.py +4 -4
- dataloom_engine-0.4.0/dataloom_engine/_optional.py +25 -0
- dataloom_engine-0.4.0/dataloom_engine/config.py +52 -0
- dataloom_engine-0.4.0/dataloom_engine/exceptions.py +25 -0
- dataloom_engine-0.4.0/dataloom_engine/hooks.py +50 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/logs.py +6 -6
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/loom.py +30 -30
- dataloom_engine-0.4.0/dataloom_engine/processors.py +53 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/sinks.py +35 -35
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/sources.py +12 -7
- dataloom_engine-0.4.0/dataloom_engine/types.py +18 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/weaver.py +17 -16
- dataloom_engine-0.4.0/dataloom_engine.egg-info/PKG-INFO +214 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine.egg-info/SOURCES.txt +2 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine.egg-info/requires.txt +4 -1
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/pyproject.toml +9 -5
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/tests/test_core.py +29 -29
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/tests/test_loom.py +27 -27
- dataloom_engine-0.4.0/tests/test_optional.py +82 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/tests/test_sinks.py +14 -14
- dataloom_engine-0.3.0/PKG-INFO +0 -196
- dataloom_engine-0.3.0/README.md +0 -164
- dataloom_engine-0.3.0/dataloom_engine/config.py +0 -52
- dataloom_engine-0.3.0/dataloom_engine/exceptions.py +0 -25
- dataloom_engine-0.3.0/dataloom_engine/hooks.py +0 -50
- dataloom_engine-0.3.0/dataloom_engine/processors.py +0 -43
- dataloom_engine-0.3.0/dataloom_engine/types.py +0 -18
- dataloom_engine-0.3.0/dataloom_engine.egg-info/PKG-INFO +0 -196
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/LICENSE +0 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine/py.typed +0 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine.egg-info/dependency_links.txt +0 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/dataloom_engine.egg-info/top_level.txt +0 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/setup.cfg +0 -0
- {dataloom_engine-0.3.0 → dataloom_engine-0.4.0}/tests/test_config.py +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataloom-engine
|
|
3
|
+
Version: 0.4.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
|
+
Provides-Extra: numpy
|
|
26
|
+
Requires-Dist: numpy>=1.21.0; extra == "numpy"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
32
|
+
Requires-Dist: numpy>=1.21.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# 🧵 DataLoom
|
|
36
|
+
|
|
37
|
+
> A lightweight, efficient and safe multi-threaded orchestration engine for Python.
|
|
38
|
+
|
|
39
|
+
🇧🇷 [Versão em português](README.pt-BR.md)
|
|
40
|
+
|
|
41
|
+
[](https://github.com/dionipadilha/dataloom/actions/workflows/ci.yml) [](https://pypi.org/project/dataloom-engine/) [](https://pypi.org/project/dataloom-engine/) 
|
|
42
|
+
|
|
43
|
+
<img width="2752" height="1536" alt="DataLoom: a lightweight, efficient and safe multi-threaded orchestration engine for Python." src="https://github.com/user-attachments/assets/c1f3fc98-5eaf-4add-a38c-2717ae699cd5" />
|
|
44
|
+
|
|
45
|
+
**DataLoom** is a library designed to process data streams using the **Producer-Consumer** pattern with multiple threads (Weavers). It abstracts away the complexity of queues, locks and lifecycle management, letting you focus purely on your data transformation logic.
|
|
46
|
+
|
|
47
|
+
## Why DataLoom?
|
|
48
|
+
⚡ Performance: real parallel processing for I/O-bound workloads.
|
|
49
|
+
🧩 Simplicity: an intuitive API inspired by the weaving metaphor.
|
|
50
|
+
🛡️ Safety: thread-safety guaranteed by design across the whole pipeline.
|
|
51
|
+
📦 Lightweight: zero-dependency core, ready to run in any Python environment.
|
|
52
|
+
|
|
53
|
+
### Why not just `ThreadPoolExecutor`?
|
|
54
|
+
|
|
55
|
+
Fair question — the stdlib solves the *parallelism*, but not the *pipeline*.
|
|
56
|
+
`concurrent.futures` gives you a thread pool; everything around it is on you:
|
|
57
|
+
|
|
58
|
+
| You need... | With the stdlib | With DataLoom |
|
|
59
|
+
| -------------------------------------------------- | --------------------------- | ----------------------------------- |
|
|
60
|
+
| A continuous producer-consumer flow | `Queue` + hand-rolled loops | `Loom` + `Source` |
|
|
61
|
+
| Backpressure (producer faster than consumers) | Manual `Queue(maxsize=...)` | Built-in, configurable |
|
|
62
|
+
| Clean shutdown (drain queue, close resources) | Manual sentinels and joins | `stop()` / `with Loom(...)` |
|
|
63
|
+
| Workers that survive errors and report them | try/except in every worker | Centralized `hooks.on_error` |
|
|
64
|
+
| Per-item processing metrics | Manual instrumentation | `hooks.on_batch_processed` |
|
|
65
|
+
| Safe concurrent file writes | Manual locking | Ready-made sinks (JSON, CSV, callback) |
|
|
66
|
+
|
|
67
|
+
If your use case is "apply a function to a list and collect the results",
|
|
68
|
+
use `ThreadPoolExecutor.map` — it is the right tool. DataLoom is for
|
|
69
|
+
**continuous or long-running flows** where lifecycle, resilience and
|
|
70
|
+
observability matter. And, like every thread-based solution on CPython,
|
|
71
|
+
the parallelism gains apply to **I/O-bound** workloads; for CPU-bound
|
|
72
|
+
work, prefer multiprocessing.
|
|
73
|
+
|
|
74
|
+
## ✨ Features
|
|
75
|
+
|
|
76
|
+
- **Cohesive API:** aligned concepts (`Loom`, `Weaver`, `Sink`).
|
|
77
|
+
- **Concurrent:** automatically manages multiple _Weavers_ (threads) in parallel.
|
|
78
|
+
- **Safe:** built-in sinks are thread-safe and exceptions are typed (`LoomError`).
|
|
79
|
+
- **Resilient:** processing errors don't kill the Weavers and are reported through hooks.
|
|
80
|
+
- **Manageable:** `Loom` is a context manager — `with Loom(...) as loom:` guarantees automatic `stop()`.
|
|
81
|
+
- **Backpressure:** the task queue is bounded by default (`queue_maxsize`), preventing unbounded memory growth.
|
|
82
|
+
- **Observable:** lifecycle hooks and per-batch metrics (`on_batch_processed` with result and duration), plus built-in logging.
|
|
83
|
+
|
|
84
|
+
## 📦 Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install dataloom-engine
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The engine core has **zero dependencies**. The demo `RandomNumPySource` and
|
|
91
|
+
`StatisticsProcessor` (used in the quick start below) need NumPy, which
|
|
92
|
+
ships as an optional extra:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install "dataloom-engine[numpy]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> ⚠️ **Mind the name:** the package installs the `dataloom_engine` module
|
|
99
|
+
> (`import dataloom_engine`). Don't confuse it with the `dataloom` package
|
|
100
|
+
> on PyPI, which is an ORM by another author and unrelated to this project.
|
|
101
|
+
|
|
102
|
+
To develop or use the latest version from the repository:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git clone https://github.com/dionipadilha/dataloom.git
|
|
106
|
+
cd dataloom
|
|
107
|
+
pip install -e .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 🚀 Quick Start
|
|
111
|
+
|
|
112
|
+
Here is a complete example of weaving a data pipeline:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
import numpy as np
|
|
117
|
+
from dataloom_engine import (
|
|
118
|
+
Loom,
|
|
119
|
+
LoomConfig,
|
|
120
|
+
LoomLogs,
|
|
121
|
+
Processor,
|
|
122
|
+
JsonFileSink,
|
|
123
|
+
ThreadedBufferedSink
|
|
124
|
+
)
|
|
125
|
+
from dataloom_engine.sources import RandomNumPySource
|
|
126
|
+
|
|
127
|
+
# 1. Define your processing logic (stateless)
|
|
128
|
+
class MyFilterProcessor(Processor):
|
|
129
|
+
def process(self, batch: np.ndarray) -> dict:
|
|
130
|
+
# 'batch' is a numpy array sized according to the config
|
|
131
|
+
avg = float(batch.mean())
|
|
132
|
+
return {
|
|
133
|
+
"processed_items": len(batch),
|
|
134
|
+
"average_value": avg,
|
|
135
|
+
"status": "high" if avg > 0.5 else "low"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 2. Initial setup
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
# Configure console logging
|
|
141
|
+
LoomLogs.setup()
|
|
142
|
+
|
|
143
|
+
# Engine parameters
|
|
144
|
+
config = LoomConfig(
|
|
145
|
+
output_dir=Path("./data_out"),
|
|
146
|
+
batch_size=100, # Process 100 items at a time
|
|
147
|
+
interval_seconds=1 # Produce a new batch every second
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# 3. Data source
|
|
151
|
+
# Decoupled from the engine, enabling custom sources (DB, CSV, S3)
|
|
152
|
+
source = RandomNumPySource(config)
|
|
153
|
+
|
|
154
|
+
# 4. Buffered file sink
|
|
155
|
+
# ThreadedBufferedSink keeps I/O from blocking the processing
|
|
156
|
+
file_sink = JsonFileSink(config.output_dir)
|
|
157
|
+
sink = ThreadedBufferedSink(file_sink)
|
|
158
|
+
|
|
159
|
+
# 5. Initialize the Loom (the orchestrator)
|
|
160
|
+
loom = Loom(
|
|
161
|
+
config=config,
|
|
162
|
+
processor=MyFilterProcessor(),
|
|
163
|
+
sink=sink,
|
|
164
|
+
source=source,
|
|
165
|
+
num_weavers=4 # 4 threads working in parallel
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
print("🧵 DataLoom started! Press Ctrl+C to stop.")
|
|
169
|
+
try:
|
|
170
|
+
# The context manager guarantees stop() and resource cleanup,
|
|
171
|
+
# even on exceptions or Ctrl+C
|
|
172
|
+
with loom:
|
|
173
|
+
loom.start()
|
|
174
|
+
except KeyboardInterrupt:
|
|
175
|
+
print("\n🛑 Loom stopped.")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 🏗️ Architecture
|
|
179
|
+
|
|
180
|
+
DataLoom is built around a weaving metaphor:
|
|
181
|
+
|
|
182
|
+
- **Loom:** the main machine. Manages the task queue and the lifecycle.
|
|
183
|
+
- **Weaver:** the worker threads. They take the raw material (a batch), process it and deliver it.
|
|
184
|
+
- **Processor:** the business logic. Turns raw data into information.
|
|
185
|
+
- **Sink:** the final destination. Where the finished product is deposited (e.g. `JsonFileSink`, `CsvFileSink`, or any destination via `CallbackSink`).
|
|
186
|
+
|
|
187
|
+
## 🛠️ Development and Testing
|
|
188
|
+
|
|
189
|
+
To contribute to the project or run the test suite:
|
|
190
|
+
|
|
191
|
+
1. Install the development dependencies:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pip install -e ".[dev]"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
2. Run the test suite (via pytest):
|
|
198
|
+
```bash
|
|
199
|
+
pytest
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
3. Run the linter and type checker:
|
|
203
|
+
```bash
|
|
204
|
+
ruff check .
|
|
205
|
+
mypy
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## 📄 License
|
|
209
|
+
|
|
210
|
+
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
|
|
211
|
+
|
|
212
|
+
## 🗒️ Version History
|
|
213
|
+
|
|
214
|
+
Changes in each release are documented in the [CHANGELOG](CHANGELOG.md).
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# 🧵 DataLoom
|
|
2
|
+
|
|
3
|
+
> A lightweight, efficient and safe multi-threaded orchestration engine for Python.
|
|
4
|
+
|
|
5
|
+
🇧🇷 [Versão em português](README.pt-BR.md)
|
|
6
|
+
|
|
7
|
+
[](https://github.com/dionipadilha/dataloom/actions/workflows/ci.yml) [](https://pypi.org/project/dataloom-engine/) [](https://pypi.org/project/dataloom-engine/) 
|
|
8
|
+
|
|
9
|
+
<img width="2752" height="1536" alt="DataLoom: a lightweight, efficient and safe multi-threaded orchestration engine for Python." src="https://github.com/user-attachments/assets/c1f3fc98-5eaf-4add-a38c-2717ae699cd5" />
|
|
10
|
+
|
|
11
|
+
**DataLoom** is a library designed to process data streams using the **Producer-Consumer** pattern with multiple threads (Weavers). It abstracts away the complexity of queues, locks and lifecycle management, letting you focus purely on your data transformation logic.
|
|
12
|
+
|
|
13
|
+
## Why DataLoom?
|
|
14
|
+
⚡ Performance: real parallel processing for I/O-bound workloads.
|
|
15
|
+
🧩 Simplicity: an intuitive API inspired by the weaving metaphor.
|
|
16
|
+
🛡️ Safety: thread-safety guaranteed by design across the whole pipeline.
|
|
17
|
+
📦 Lightweight: zero-dependency core, ready to run in any Python environment.
|
|
18
|
+
|
|
19
|
+
### Why not just `ThreadPoolExecutor`?
|
|
20
|
+
|
|
21
|
+
Fair question — the stdlib solves the *parallelism*, but not the *pipeline*.
|
|
22
|
+
`concurrent.futures` gives you a thread pool; everything around it is on you:
|
|
23
|
+
|
|
24
|
+
| You need... | With the stdlib | With DataLoom |
|
|
25
|
+
| -------------------------------------------------- | --------------------------- | ----------------------------------- |
|
|
26
|
+
| A continuous producer-consumer flow | `Queue` + hand-rolled loops | `Loom` + `Source` |
|
|
27
|
+
| Backpressure (producer faster than consumers) | Manual `Queue(maxsize=...)` | Built-in, configurable |
|
|
28
|
+
| Clean shutdown (drain queue, close resources) | Manual sentinels and joins | `stop()` / `with Loom(...)` |
|
|
29
|
+
| Workers that survive errors and report them | try/except in every worker | Centralized `hooks.on_error` |
|
|
30
|
+
| Per-item processing metrics | Manual instrumentation | `hooks.on_batch_processed` |
|
|
31
|
+
| Safe concurrent file writes | Manual locking | Ready-made sinks (JSON, CSV, callback) |
|
|
32
|
+
|
|
33
|
+
If your use case is "apply a function to a list and collect the results",
|
|
34
|
+
use `ThreadPoolExecutor.map` — it is the right tool. DataLoom is for
|
|
35
|
+
**continuous or long-running flows** where lifecycle, resilience and
|
|
36
|
+
observability matter. And, like every thread-based solution on CPython,
|
|
37
|
+
the parallelism gains apply to **I/O-bound** workloads; for CPU-bound
|
|
38
|
+
work, prefer multiprocessing.
|
|
39
|
+
|
|
40
|
+
## ✨ Features
|
|
41
|
+
|
|
42
|
+
- **Cohesive API:** aligned concepts (`Loom`, `Weaver`, `Sink`).
|
|
43
|
+
- **Concurrent:** automatically manages multiple _Weavers_ (threads) in parallel.
|
|
44
|
+
- **Safe:** built-in sinks are thread-safe and exceptions are typed (`LoomError`).
|
|
45
|
+
- **Resilient:** processing errors don't kill the Weavers and are reported through hooks.
|
|
46
|
+
- **Manageable:** `Loom` is a context manager — `with Loom(...) as loom:` guarantees automatic `stop()`.
|
|
47
|
+
- **Backpressure:** the task queue is bounded by default (`queue_maxsize`), preventing unbounded memory growth.
|
|
48
|
+
- **Observable:** lifecycle hooks and per-batch metrics (`on_batch_processed` with result and duration), plus built-in logging.
|
|
49
|
+
|
|
50
|
+
## 📦 Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install dataloom-engine
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The engine core has **zero dependencies**. The demo `RandomNumPySource` and
|
|
57
|
+
`StatisticsProcessor` (used in the quick start below) need NumPy, which
|
|
58
|
+
ships as an optional extra:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install "dataloom-engine[numpy]"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> ⚠️ **Mind the name:** the package installs the `dataloom_engine` module
|
|
65
|
+
> (`import dataloom_engine`). Don't confuse it with the `dataloom` package
|
|
66
|
+
> on PyPI, which is an ORM by another author and unrelated to this project.
|
|
67
|
+
|
|
68
|
+
To develop or use the latest version from the repository:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/dionipadilha/dataloom.git
|
|
72
|
+
cd dataloom
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🚀 Quick Start
|
|
77
|
+
|
|
78
|
+
Here is a complete example of weaving a data pipeline:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
import numpy as np
|
|
83
|
+
from dataloom_engine import (
|
|
84
|
+
Loom,
|
|
85
|
+
LoomConfig,
|
|
86
|
+
LoomLogs,
|
|
87
|
+
Processor,
|
|
88
|
+
JsonFileSink,
|
|
89
|
+
ThreadedBufferedSink
|
|
90
|
+
)
|
|
91
|
+
from dataloom_engine.sources import RandomNumPySource
|
|
92
|
+
|
|
93
|
+
# 1. Define your processing logic (stateless)
|
|
94
|
+
class MyFilterProcessor(Processor):
|
|
95
|
+
def process(self, batch: np.ndarray) -> dict:
|
|
96
|
+
# 'batch' is a numpy array sized according to the config
|
|
97
|
+
avg = float(batch.mean())
|
|
98
|
+
return {
|
|
99
|
+
"processed_items": len(batch),
|
|
100
|
+
"average_value": avg,
|
|
101
|
+
"status": "high" if avg > 0.5 else "low"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# 2. Initial setup
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
# Configure console logging
|
|
107
|
+
LoomLogs.setup()
|
|
108
|
+
|
|
109
|
+
# Engine parameters
|
|
110
|
+
config = LoomConfig(
|
|
111
|
+
output_dir=Path("./data_out"),
|
|
112
|
+
batch_size=100, # Process 100 items at a time
|
|
113
|
+
interval_seconds=1 # Produce a new batch every second
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# 3. Data source
|
|
117
|
+
# Decoupled from the engine, enabling custom sources (DB, CSV, S3)
|
|
118
|
+
source = RandomNumPySource(config)
|
|
119
|
+
|
|
120
|
+
# 4. Buffered file sink
|
|
121
|
+
# ThreadedBufferedSink keeps I/O from blocking the processing
|
|
122
|
+
file_sink = JsonFileSink(config.output_dir)
|
|
123
|
+
sink = ThreadedBufferedSink(file_sink)
|
|
124
|
+
|
|
125
|
+
# 5. Initialize the Loom (the orchestrator)
|
|
126
|
+
loom = Loom(
|
|
127
|
+
config=config,
|
|
128
|
+
processor=MyFilterProcessor(),
|
|
129
|
+
sink=sink,
|
|
130
|
+
source=source,
|
|
131
|
+
num_weavers=4 # 4 threads working in parallel
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
print("🧵 DataLoom started! Press Ctrl+C to stop.")
|
|
135
|
+
try:
|
|
136
|
+
# The context manager guarantees stop() and resource cleanup,
|
|
137
|
+
# even on exceptions or Ctrl+C
|
|
138
|
+
with loom:
|
|
139
|
+
loom.start()
|
|
140
|
+
except KeyboardInterrupt:
|
|
141
|
+
print("\n🛑 Loom stopped.")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 🏗️ Architecture
|
|
145
|
+
|
|
146
|
+
DataLoom is built around a weaving metaphor:
|
|
147
|
+
|
|
148
|
+
- **Loom:** the main machine. Manages the task queue and the lifecycle.
|
|
149
|
+
- **Weaver:** the worker threads. They take the raw material (a batch), process it and deliver it.
|
|
150
|
+
- **Processor:** the business logic. Turns raw data into information.
|
|
151
|
+
- **Sink:** the final destination. Where the finished product is deposited (e.g. `JsonFileSink`, `CsvFileSink`, or any destination via `CallbackSink`).
|
|
152
|
+
|
|
153
|
+
## 🛠️ Development and Testing
|
|
154
|
+
|
|
155
|
+
To contribute to the project or run the test suite:
|
|
156
|
+
|
|
157
|
+
1. Install the development dependencies:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install -e ".[dev]"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
2. Run the test suite (via pytest):
|
|
164
|
+
```bash
|
|
165
|
+
pytest
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
3. Run the linter and type checker:
|
|
169
|
+
```bash
|
|
170
|
+
ruff check .
|
|
171
|
+
mypy
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 📄 License
|
|
175
|
+
|
|
176
|
+
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
|
|
177
|
+
|
|
178
|
+
## 🗒️ Version History
|
|
179
|
+
|
|
180
|
+
Changes in each release are documented in the [CHANGELOG](CHANGELOG.md).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# dataloom_engine/__init__.py
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
DataLoom:
|
|
4
|
+
DataLoom: a lightweight and efficient multi-threaded orchestration engine.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
This module exposes the package's public API. Note that internal classes
|
|
7
|
+
such as 'Weaver' are deliberately not exposed, keeping the usage surface
|
|
8
|
+
clean and safe for consumers.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from dataloom_engine.config import LoomConfig
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# dataloom_engine/_optional.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Helpers for optional dependencies.
|
|
5
|
+
The engine core has no third-party requirements; the demo implementations
|
|
6
|
+
that need one import it lazily through these helpers, so that a missing
|
|
7
|
+
dependency fails fast with an actionable message.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def require_numpy(feature: str) -> ModuleType:
|
|
14
|
+
"""
|
|
15
|
+
Returns the numpy module, or raises an informative ImportError if the
|
|
16
|
+
optional dependency is not installed.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
import numpy
|
|
20
|
+
except ImportError as exc:
|
|
21
|
+
raise ImportError(
|
|
22
|
+
f"{feature} requires numpy, which is an optional dependency of "
|
|
23
|
+
'dataloom-engine. Install it with: pip install "dataloom-engine[numpy]"'
|
|
24
|
+
) from exc
|
|
25
|
+
return numpy
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# dataloom_engine/config.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Orchestrator configuration management.
|
|
5
|
+
Centralizes operational parameters such as directories and batch sizes.
|
|
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
|
+
DataLoom's main configuration object.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
output_dir (Path): Base directory where the built-in Sinks save data.
|
|
22
|
+
Strings are converted to Path automatically.
|
|
23
|
+
batch_size (int): Number of items produced per processing cycle.
|
|
24
|
+
interval_seconds (float): Time interval between task generations.
|
|
25
|
+
queue_maxsize (Optional[int]): Maximum capacity of the task queue
|
|
26
|
+
(backpressure). None uses the Loom default (num_weavers * 4);
|
|
27
|
+
0 means an unbounded queue.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ConfigurationError: if any parameter is invalid.
|
|
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 must be greater than zero (got: {self.batch_size})."
|
|
44
|
+
)
|
|
45
|
+
if self.interval_seconds < 0:
|
|
46
|
+
raise ConfigurationError(
|
|
47
|
+
f"interval_seconds cannot be negative (got: {self.interval_seconds})."
|
|
48
|
+
)
|
|
49
|
+
if self.queue_maxsize is not None and self.queue_maxsize < 0:
|
|
50
|
+
raise ConfigurationError(
|
|
51
|
+
f"queue_maxsize cannot be negative (got: {self.queue_maxsize})."
|
|
52
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# dataloom_engine/exceptions.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Custom exceptions for DataLoom.
|
|
5
|
+
Lets consumers catch library-specific errors without relying on
|
|
6
|
+
generic Python exceptions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoomError(Exception):
|
|
11
|
+
"""Base exception for every DataLoom error."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigurationError(LoomError):
|
|
17
|
+
"""Raised when LoomConfig validation fails."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WeaverError(LoomError):
|
|
23
|
+
"""Raised when a Weaver fails to process a batch."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# dataloom_engine/hooks.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Observability extension points (Hooks).
|
|
5
|
+
Lets you inject monitoring logic without coupling it to the engine core.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoomHooks:
|
|
12
|
+
"""
|
|
13
|
+
Base class for lifecycle hooks.
|
|
14
|
+
Every method is optional and a no-op by default: override only the
|
|
15
|
+
points you care about. (Deliberately not an ABC — there is no method
|
|
16
|
+
you are required to implement.)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def on_start(self) -> None:
|
|
20
|
+
"""Called right before the Weavers are started."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def on_stop(self) -> None:
|
|
24
|
+
"""Called after the orchestrator shuts down gracefully."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def on_error(self, error: Exception) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Called when an exception occurs in the Loom's main loop or while
|
|
30
|
+
processing a batch inside a Weaver (WeaverError).
|
|
31
|
+
|
|
32
|
+
Note: it may be invoked from multiple Weaver threads at the same
|
|
33
|
+
time — implementations must be thread-safe.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def on_batch_processed(self, result: Dict[str, Any], duration_seconds: float) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Called after each batch is processed and delivered to the Sink.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
result: The dictionary produced by the Processor for the batch.
|
|
43
|
+
duration_seconds: Time spent in process() + send(), measured
|
|
44
|
+
with a monotonic clock.
|
|
45
|
+
|
|
46
|
+
Note: like on_error, this is invoked from the Weaver threads —
|
|
47
|
+
implementations must be thread-safe and fast, since they run on
|
|
48
|
+
the pipeline's hot path.
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# dataloom_engine/logs.py
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Logging configuration utilities for DataLoom.
|
|
5
|
+
Provides a static namespace for quick setup.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
@@ -10,16 +10,16 @@ import logging
|
|
|
10
10
|
|
|
11
11
|
class LoomLogs:
|
|
12
12
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Utility namespace for DataLoom log management.
|
|
14
|
+
Not meant to be instantiated — use it statically.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
@staticmethod
|
|
18
18
|
def setup(level: int = logging.INFO) -> None:
|
|
19
19
|
"""
|
|
20
|
-
|
|
20
|
+
Configures basic logging to stdout.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Usage:
|
|
23
23
|
LoomLogs.setup(level=logging.DEBUG)
|
|
24
24
|
"""
|
|
25
25
|
logging.basicConfig(
|