abstract-block-dumper 0.0.1__tar.gz → 0.0.4__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.
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.gitignore +2 -1
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/CHANGELOG.md +15 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/PKG-INFO +127 -52
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/README.md +126 -51
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/README.md +1 -1
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/tasks.py +1 -1
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/docker-compose.yml +1 -1
- abstract_block_dumper-0.0.4/example_project/example_project/celery.py +26 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/example_project/settings.py +10 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/dal/django_dal.py +4 -3
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/dal/memory_registry.py +12 -20
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/discovery.py +1 -1
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/exceptions.py +1 -1
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/services/block_processor.py +15 -14
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/services/executor.py +2 -2
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/services/scheduler.py +9 -7
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/services/utils.py +3 -7
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/_version.py +2 -2
- abstract_block_dumper-0.0.1/src/abstract_block_dumper/management/commands/block_tasks.py → abstract_block_dumper-0.0.4/src/abstract_block_dumper/management/commands/block_tasks_v1.py +6 -3
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/models.py +1 -1
- abstract_block_dumper-0.0.4/src/abstract_block_dumper/v1/celery.py +53 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/v1}/decorators.py +24 -29
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper → abstract_block_dumper-0.0.4/src/abstract_block_dumper/v1}/tasks.py +3 -3
- abstract_block_dumper-0.0.4/tests/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/conftest.py +2 -2
- abstract_block_dumper-0.0.4/tests/integration/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_block_processor.py +3 -3
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_concurrent_processing.py +6 -6
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_multi_arguments_tasks.py +4 -4
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_registered_celery_tasks.py +5 -5
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_scheduler.py +5 -5
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/integration/test_task_registration.py +4 -4
- abstract_block_dumper-0.0.4/tests/unit/test_celery_integration.py +17 -0
- abstract_block_dumper-0.0.4/tests/unit/test_decorator.py +0 -0
- abstract_block_dumper-0.0.1/example_project/example_project/celery.py +0 -12
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.cruft.json +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.github/dependabot.yml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.github/workflows/ci.yml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.github/workflows/publish.yml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.pre-commit-config.yaml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/.shellcheckrc +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/SECURITY.md +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/docs/3rd_party/cookiecutter-rt-pkg/CHANGELOG.md +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/.dockerignore +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/.gitignore +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/Dockerfile +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/admin.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/apps.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/management/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/management/commands/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/management/commands/create_admin.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/migrations/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/models.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/tests.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/block_explorer/views.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/example_project/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/example_project/asgi.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/example_project/urls.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/example_project/wsgi.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/main.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/manage.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/pyproject.toml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/pytest.ini +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/example_project/uv.lock +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/noxfile.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/pyproject.toml +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/__init__.py +0 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper/dal → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper/management → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal/dal}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper/migrations → abstract_block_dumper-0.0.4/src/abstract_block_dumper/_internal/services}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/admin.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/apps.py +0 -0
- {abstract_block_dumper-0.0.1/src/abstract_block_dumper/services → abstract_block_dumper-0.0.4/src/abstract_block_dumper/management}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1/tests → abstract_block_dumper-0.0.4/src/abstract_block_dumper/management/commands}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/migrations/0001_initial.py +0 -0
- {abstract_block_dumper-0.0.1/tests/integration → abstract_block_dumper-0.0.4/src/abstract_block_dumper/migrations}/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/src/abstract_block_dumper/py.typed +0 -0
- /abstract_block_dumper-0.0.1/tests/unit/test_decorator.py → /abstract_block_dumper-0.0.4/src/abstract_block_dumper/v1/__init__.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/django_fixtures.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/fatories.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/tests/settings.py +0 -0
- {abstract_block_dumper-0.0.1 → abstract_block_dumper-0.0.4}/uv.lock +0 -0
|
@@ -9,6 +9,21 @@ upcoming release can be found in [changelog.d](changelog.d).
|
|
|
9
9
|
|
|
10
10
|
<!-- towncrier release notes start -->
|
|
11
11
|
|
|
12
|
+
## [0.0.4](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.4) - 2025-11-17
|
|
13
|
+
|
|
14
|
+
No significant changes.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.0.5](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.5) - 2025-11-17
|
|
18
|
+
|
|
19
|
+
No significant changes.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## [0.0.2](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.2) - 2025-10-24
|
|
23
|
+
|
|
24
|
+
No significant changes.
|
|
25
|
+
|
|
26
|
+
|
|
12
27
|
## [0.0.1](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.1) - 2025-10-21
|
|
13
28
|
|
|
14
29
|
No significant changes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstract-block-dumper
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Project-URL: Source, https://github.com/bactensor/abstract-block-dumper
|
|
5
5
|
Project-URL: Issue Tracker, https://github.com/bactensor/abstract-block-dumper/issues
|
|
6
6
|
Author-email: Reef Technologies <opensource@reef.pl>
|
|
@@ -26,6 +26,22 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
This package provides a simplified framework for creating block processing tasks in Django applications.
|
|
27
27
|
Define tasks with lambda conditions using the @block_task decorator and run them asynchronously with Celery.
|
|
28
28
|
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
> [!IMPORTANT]
|
|
32
|
+
> This package uses [ApiVer](#versioning), make sure to import `abstract_block_dumper.v1`.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Versioning
|
|
36
|
+
|
|
37
|
+
This package uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
38
|
+
TL;DR you are safe to use [compatible release version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release) `~=MAJOR.MINOR` in your `pyproject.toml` or `requirements.txt`.
|
|
39
|
+
|
|
40
|
+
Additionally, this package uses [ApiVer](https://www.youtube.com/watch?v=FgcoAKchPjk) to further reduce the risk of breaking changes.
|
|
41
|
+
This means, the public API of this package is explicitly versioned, e.g. `abstract_block_dumper.v1`, and will not change in a backwards-incompatible way even when `abstract_block_dumper.v2` is released.
|
|
42
|
+
|
|
43
|
+
Internal packages, i.e. prefixed by `abstract_block_dumper._` do not share these guarantees and may change in a backwards-incompatible way at any time even in patch releases.
|
|
44
|
+
|
|
29
45
|
## Implementation Details
|
|
30
46
|
|
|
31
47
|
### General Workflow:
|
|
@@ -82,6 +98,28 @@ INSTALLED_APPS = [
|
|
|
82
98
|
python manage.py migrate
|
|
83
99
|
```
|
|
84
100
|
|
|
101
|
+
4. **Configure Celery to discover block tasks:**
|
|
102
|
+
|
|
103
|
+
In your project's `celery.py` file, add the following to ensure Celery workers can discover your `@block_task` decorated functions:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from celery import Celery
|
|
107
|
+
from celery.signals import worker_ready
|
|
108
|
+
from django.conf import settings
|
|
109
|
+
|
|
110
|
+
app = Celery('your_project')
|
|
111
|
+
app.config_from_object('django.conf:settings', namespace='CELERY')
|
|
112
|
+
app.autodiscover_tasks()
|
|
113
|
+
|
|
114
|
+
@worker_ready.connect
|
|
115
|
+
def on_worker_ready(**kwargs):
|
|
116
|
+
"""Load block tasks when worker starts."""
|
|
117
|
+
from abstract_block_dumper.v1.celery import setup_celery_tasks
|
|
118
|
+
setup_celery_tasks()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> **Important:** Without this step, Celery workers will not recognize your `@block_task` decorated functions, and you'll see "Received unregistered task" errors.
|
|
122
|
+
|
|
85
123
|
## Usage
|
|
86
124
|
|
|
87
125
|
### 1. Define Block Processing Tasks
|
|
@@ -93,7 +131,7 @@ Create block processing tasks in `tasks.py` or `block_tasks.py` file inside any
|
|
|
93
131
|
### 3. Start the Block Scheduler
|
|
94
132
|
Run the scheduler to start processing blocks:
|
|
95
133
|
```bash
|
|
96
|
-
$ python manage.py
|
|
134
|
+
$ python manage.py block_tasks_v1
|
|
97
135
|
```
|
|
98
136
|
|
|
99
137
|
This command will:
|
|
@@ -112,7 +150,7 @@ See examples below:
|
|
|
112
150
|
Use the `@block_task` decorator with lambda conditions to create block processing tasks:
|
|
113
151
|
|
|
114
152
|
```python
|
|
115
|
-
from abstract_block_dumper.decorators import block_task
|
|
153
|
+
from abstract_block_dumper.v1.decorators import block_task
|
|
116
154
|
|
|
117
155
|
|
|
118
156
|
# Process every block
|
|
@@ -144,7 +182,7 @@ def process_multi_netuid_task(block_number: int, netuid: int):
|
|
|
144
182
|
The framework provides a maintenance task to clean up old task records and maintain database performance:
|
|
145
183
|
|
|
146
184
|
```python
|
|
147
|
-
from abstract_block_dumper.tasks import cleanup_old_tasks
|
|
185
|
+
from abstract_block_dumper.v1.tasks import cleanup_old_tasks
|
|
148
186
|
|
|
149
187
|
# Delete tasks older than 7 days (default)
|
|
150
188
|
cleanup_old_tasks.delay()
|
|
@@ -160,13 +198,13 @@ This task deletes all succeeded or unrecoverable failed tasks older than the spe
|
|
|
160
198
|
**Option 1: Manual Execution**
|
|
161
199
|
```bash
|
|
162
200
|
# Using Django shell
|
|
163
|
-
python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
201
|
+
python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
164
202
|
```
|
|
165
203
|
|
|
166
204
|
**Option 2: Cron Job (Recommended - once per day)**
|
|
167
205
|
```bash
|
|
168
206
|
# Add to crontab (daily at 2 AM)
|
|
169
|
-
0 2 * * * cd /path/to/your/project && python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
207
|
+
0 2 * * * cd /path/to/your/project && python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
170
208
|
```
|
|
171
209
|
|
|
172
210
|
**Option 3: Celery Beat (Automated Scheduling)**
|
|
@@ -210,55 +248,92 @@ BLOCK_DUMPER_MAX_ATTEMPTS = 3 # maximum retry attempts
|
|
|
210
248
|
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 1440 # maximum retry delay (24 hours)
|
|
211
249
|
```
|
|
212
250
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- **
|
|
226
|
-
- **
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
**
|
|
242
|
-
- **
|
|
243
|
-
- **
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
251
|
+
## Configuration Options Reference
|
|
252
|
+
|
|
253
|
+
### `BITTENSOR_NETWORK`
|
|
254
|
+
- **Type:** `str`
|
|
255
|
+
- **Default:** `'finney'`
|
|
256
|
+
- **Description:** Specifies which [Bittensor network](https://docs.learnbittensor.org/concepts/bittensor-networks) to connect to
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### `BLOCK_DUMPER_START_FROM_BLOCK`
|
|
261
|
+
- **Type:** `str | int | None`
|
|
262
|
+
- **Default:** `None`
|
|
263
|
+
- **Valid Range:** `None`, `'current'`, or any positive integer
|
|
264
|
+
- **Description:** Determines the starting block for processing when the scheduler first runs
|
|
265
|
+
- `None` → Resume from the last processed block stored in database
|
|
266
|
+
- `'current'` → Start from the current blockchain block (skips historical blocks)
|
|
267
|
+
- Integer → Start from a specific block number (e.g., `1000000`)
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
BLOCK_DUMPER_START_FROM_BLOCK = 'current'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
> **Performance Impact:** Starting from historical blocks may require significant processing time
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### `BLOCK_DUMPER_POLL_INTERVAL`
|
|
278
|
+
- **Type:** `int`
|
|
279
|
+
- **Default:** `1`
|
|
280
|
+
- **Valid Range:** `1` to `3600` (seconds)
|
|
281
|
+
- **Description:** Seconds to wait between checking for new blocks
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
BLOCK_DUMPER_POLL_INTERVAL = 5
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
> **Performance Impact:**
|
|
288
|
+
> - Lower values (1-2s): Near real-time processing, higher CPU/network usage
|
|
289
|
+
> - Higher values (10-60s): Reduced load but delayed processing
|
|
290
|
+
> - Very low values (<1s): May cause rate limiting
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
### `BLOCK_DUMPER_MAX_ATTEMPTS`
|
|
295
|
+
- **Type:** `int`
|
|
296
|
+
- **Default:** `3`
|
|
297
|
+
- **Valid Range:** `1` to `10`
|
|
298
|
+
- **Description:** Maximum number of attempts to retry a failed task before giving up
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
BLOCK_DUMPER_MAX_ATTEMPTS = 5
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
> **Performance Impact:** Higher values increase resilience but may delay failure detection
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### `BLOCK_TASK_RETRY_BACKOFF`
|
|
309
|
+
- **Type:** `int`
|
|
310
|
+
- **Default:** `1`
|
|
311
|
+
- **Valid Range:** `1` to `60` (minutes)
|
|
312
|
+
- **Description:** Base number of minutes for exponential backoff retry delays
|
|
313
|
+
- **Calculation:** Actual delay = `backoff ** attempt_count` minutes
|
|
252
314
|
- Attempt 1: 2¹ = 2 minutes
|
|
253
|
-
- Attempt 2: 2² = 4 minutes
|
|
315
|
+
- Attempt 2: 2² = 4 minutes
|
|
254
316
|
- Attempt 3: 2³ = 8 minutes
|
|
255
|
-
- **Performance Impact**: Lower values retry faster but may overwhelm failing services
|
|
256
317
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
318
|
+
```python
|
|
319
|
+
BLOCK_TASK_RETRY_BACKOFF = 2
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
> **Performance Impact:** Lower values retry faster but may overwhelm failing services
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### `BLOCK_TASK_MAX_RETRY_DELAY_MINUTES`
|
|
327
|
+
- **Type:** `int`
|
|
328
|
+
- **Default:** `1440` (24 hours)
|
|
329
|
+
- **Valid Range:** `1` to `10080` (1 minute to 1 week)
|
|
330
|
+
- **Description:** Maximum delay (in minutes) between retry attempts, caps exponential backoff
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 720 # 12 hours max
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
> **Performance Impact:** Prevents extremely long delays while maintaining backoff benefits
|
|
262
337
|
|
|
263
338
|
|
|
264
339
|
## Example Project
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
This package provides a simplified framework for creating block processing tasks in Django applications.
|
|
5
5
|
Define tasks with lambda conditions using the @block_task decorator and run them asynchronously with Celery.
|
|
6
6
|
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
> [!IMPORTANT]
|
|
10
|
+
> This package uses [ApiVer](#versioning), make sure to import `abstract_block_dumper.v1`.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Versioning
|
|
14
|
+
|
|
15
|
+
This package uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
16
|
+
TL;DR you are safe to use [compatible release version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release) `~=MAJOR.MINOR` in your `pyproject.toml` or `requirements.txt`.
|
|
17
|
+
|
|
18
|
+
Additionally, this package uses [ApiVer](https://www.youtube.com/watch?v=FgcoAKchPjk) to further reduce the risk of breaking changes.
|
|
19
|
+
This means, the public API of this package is explicitly versioned, e.g. `abstract_block_dumper.v1`, and will not change in a backwards-incompatible way even when `abstract_block_dumper.v2` is released.
|
|
20
|
+
|
|
21
|
+
Internal packages, i.e. prefixed by `abstract_block_dumper._` do not share these guarantees and may change in a backwards-incompatible way at any time even in patch releases.
|
|
22
|
+
|
|
7
23
|
## Implementation Details
|
|
8
24
|
|
|
9
25
|
### General Workflow:
|
|
@@ -60,6 +76,28 @@ INSTALLED_APPS = [
|
|
|
60
76
|
python manage.py migrate
|
|
61
77
|
```
|
|
62
78
|
|
|
79
|
+
4. **Configure Celery to discover block tasks:**
|
|
80
|
+
|
|
81
|
+
In your project's `celery.py` file, add the following to ensure Celery workers can discover your `@block_task` decorated functions:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from celery import Celery
|
|
85
|
+
from celery.signals import worker_ready
|
|
86
|
+
from django.conf import settings
|
|
87
|
+
|
|
88
|
+
app = Celery('your_project')
|
|
89
|
+
app.config_from_object('django.conf:settings', namespace='CELERY')
|
|
90
|
+
app.autodiscover_tasks()
|
|
91
|
+
|
|
92
|
+
@worker_ready.connect
|
|
93
|
+
def on_worker_ready(**kwargs):
|
|
94
|
+
"""Load block tasks when worker starts."""
|
|
95
|
+
from abstract_block_dumper.v1.celery import setup_celery_tasks
|
|
96
|
+
setup_celery_tasks()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Important:** Without this step, Celery workers will not recognize your `@block_task` decorated functions, and you'll see "Received unregistered task" errors.
|
|
100
|
+
|
|
63
101
|
## Usage
|
|
64
102
|
|
|
65
103
|
### 1. Define Block Processing Tasks
|
|
@@ -71,7 +109,7 @@ Create block processing tasks in `tasks.py` or `block_tasks.py` file inside any
|
|
|
71
109
|
### 3. Start the Block Scheduler
|
|
72
110
|
Run the scheduler to start processing blocks:
|
|
73
111
|
```bash
|
|
74
|
-
$ python manage.py
|
|
112
|
+
$ python manage.py block_tasks_v1
|
|
75
113
|
```
|
|
76
114
|
|
|
77
115
|
This command will:
|
|
@@ -90,7 +128,7 @@ See examples below:
|
|
|
90
128
|
Use the `@block_task` decorator with lambda conditions to create block processing tasks:
|
|
91
129
|
|
|
92
130
|
```python
|
|
93
|
-
from abstract_block_dumper.decorators import block_task
|
|
131
|
+
from abstract_block_dumper.v1.decorators import block_task
|
|
94
132
|
|
|
95
133
|
|
|
96
134
|
# Process every block
|
|
@@ -122,7 +160,7 @@ def process_multi_netuid_task(block_number: int, netuid: int):
|
|
|
122
160
|
The framework provides a maintenance task to clean up old task records and maintain database performance:
|
|
123
161
|
|
|
124
162
|
```python
|
|
125
|
-
from abstract_block_dumper.tasks import cleanup_old_tasks
|
|
163
|
+
from abstract_block_dumper.v1.tasks import cleanup_old_tasks
|
|
126
164
|
|
|
127
165
|
# Delete tasks older than 7 days (default)
|
|
128
166
|
cleanup_old_tasks.delay()
|
|
@@ -138,13 +176,13 @@ This task deletes all succeeded or unrecoverable failed tasks older than the spe
|
|
|
138
176
|
**Option 1: Manual Execution**
|
|
139
177
|
```bash
|
|
140
178
|
# Using Django shell
|
|
141
|
-
python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
179
|
+
python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
142
180
|
```
|
|
143
181
|
|
|
144
182
|
**Option 2: Cron Job (Recommended - once per day)**
|
|
145
183
|
```bash
|
|
146
184
|
# Add to crontab (daily at 2 AM)
|
|
147
|
-
0 2 * * * cd /path/to/your/project && python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
185
|
+
0 2 * * * cd /path/to/your/project && python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
148
186
|
```
|
|
149
187
|
|
|
150
188
|
**Option 3: Celery Beat (Automated Scheduling)**
|
|
@@ -188,55 +226,92 @@ BLOCK_DUMPER_MAX_ATTEMPTS = 3 # maximum retry attempts
|
|
|
188
226
|
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 1440 # maximum retry delay (24 hours)
|
|
189
227
|
```
|
|
190
228
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
- **
|
|
204
|
-
- **
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
**
|
|
220
|
-
- **
|
|
221
|
-
- **
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
229
|
+
## Configuration Options Reference
|
|
230
|
+
|
|
231
|
+
### `BITTENSOR_NETWORK`
|
|
232
|
+
- **Type:** `str`
|
|
233
|
+
- **Default:** `'finney'`
|
|
234
|
+
- **Description:** Specifies which [Bittensor network](https://docs.learnbittensor.org/concepts/bittensor-networks) to connect to
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### `BLOCK_DUMPER_START_FROM_BLOCK`
|
|
239
|
+
- **Type:** `str | int | None`
|
|
240
|
+
- **Default:** `None`
|
|
241
|
+
- **Valid Range:** `None`, `'current'`, or any positive integer
|
|
242
|
+
- **Description:** Determines the starting block for processing when the scheduler first runs
|
|
243
|
+
- `None` → Resume from the last processed block stored in database
|
|
244
|
+
- `'current'` → Start from the current blockchain block (skips historical blocks)
|
|
245
|
+
- Integer → Start from a specific block number (e.g., `1000000`)
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
BLOCK_DUMPER_START_FROM_BLOCK = 'current'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
> **Performance Impact:** Starting from historical blocks may require significant processing time
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### `BLOCK_DUMPER_POLL_INTERVAL`
|
|
256
|
+
- **Type:** `int`
|
|
257
|
+
- **Default:** `1`
|
|
258
|
+
- **Valid Range:** `1` to `3600` (seconds)
|
|
259
|
+
- **Description:** Seconds to wait between checking for new blocks
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
BLOCK_DUMPER_POLL_INTERVAL = 5
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
> **Performance Impact:**
|
|
266
|
+
> - Lower values (1-2s): Near real-time processing, higher CPU/network usage
|
|
267
|
+
> - Higher values (10-60s): Reduced load but delayed processing
|
|
268
|
+
> - Very low values (<1s): May cause rate limiting
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `BLOCK_DUMPER_MAX_ATTEMPTS`
|
|
273
|
+
- **Type:** `int`
|
|
274
|
+
- **Default:** `3`
|
|
275
|
+
- **Valid Range:** `1` to `10`
|
|
276
|
+
- **Description:** Maximum number of attempts to retry a failed task before giving up
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
BLOCK_DUMPER_MAX_ATTEMPTS = 5
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
> **Performance Impact:** Higher values increase resilience but may delay failure detection
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### `BLOCK_TASK_RETRY_BACKOFF`
|
|
287
|
+
- **Type:** `int`
|
|
288
|
+
- **Default:** `1`
|
|
289
|
+
- **Valid Range:** `1` to `60` (minutes)
|
|
290
|
+
- **Description:** Base number of minutes for exponential backoff retry delays
|
|
291
|
+
- **Calculation:** Actual delay = `backoff ** attempt_count` minutes
|
|
230
292
|
- Attempt 1: 2¹ = 2 minutes
|
|
231
|
-
- Attempt 2: 2² = 4 minutes
|
|
293
|
+
- Attempt 2: 2² = 4 minutes
|
|
232
294
|
- Attempt 3: 2³ = 8 minutes
|
|
233
|
-
- **Performance Impact**: Lower values retry faster but may overwhelm failing services
|
|
234
295
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
296
|
+
```python
|
|
297
|
+
BLOCK_TASK_RETRY_BACKOFF = 2
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
> **Performance Impact:** Lower values retry faster but may overwhelm failing services
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
### `BLOCK_TASK_MAX_RETRY_DELAY_MINUTES`
|
|
305
|
+
- **Type:** `int`
|
|
306
|
+
- **Default:** `1440` (24 hours)
|
|
307
|
+
- **Valid Range:** `1` to `10080` (1 minute to 1 week)
|
|
308
|
+
- **Description:** Maximum delay (in minutes) between retry attempts, caps exponential backoff
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 720 # 12 hours max
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
> **Performance Impact:** Prevents extremely long delays while maintaining backoff benefits
|
|
240
315
|
|
|
241
316
|
|
|
242
317
|
## Example Project
|
|
@@ -15,5 +15,5 @@ docker-compose up --build
|
|
|
15
15
|
2. Access the Django admin interface at `http://localhost:8000/admin` with username `admin` and password `admin` (automatically created).
|
|
16
16
|
3. Start the block dumper scheduler:
|
|
17
17
|
```bash
|
|
18
|
-
docker-compose exec web python manage.py
|
|
18
|
+
docker-compose exec web python manage.py block_tasks_v1
|
|
19
19
|
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from celery import Celery
|
|
4
|
+
from celery.signals import worker_ready
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
|
|
7
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
|
|
8
|
+
|
|
9
|
+
app = Celery("example_project")
|
|
10
|
+
|
|
11
|
+
app.config_from_object(settings, namespace="CELERY")
|
|
12
|
+
|
|
13
|
+
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@worker_ready.connect
|
|
17
|
+
def on_worker_ready(**kwargs):
|
|
18
|
+
"""
|
|
19
|
+
Load block tasks when Celery worker starts.
|
|
20
|
+
|
|
21
|
+
This is required for abstract-block-dumper to register @block_task
|
|
22
|
+
decorated functions so they can receive messages from the broker.
|
|
23
|
+
"""
|
|
24
|
+
from abstract_block_dumper.v1.celery import setup_celery_tasks
|
|
25
|
+
|
|
26
|
+
setup_celery_tasks()
|
|
@@ -3,6 +3,7 @@ import sys
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import dj_database_url # type: ignore
|
|
6
|
+
from celery.schedules import crontab
|
|
6
7
|
|
|
7
8
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
8
9
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
@@ -133,3 +134,12 @@ BLOCK_DUMPER_POLL_INTERVAL = 1 # seconds - ultra-fast polling for real-time pro
|
|
|
133
134
|
BLOCK_DUMPER_START_FROM_BLOCK = "current" # None = resume from DB, 'current' = current block, or block number
|
|
134
135
|
BLOCK_TASK_RETRY_BACKOFF = 2
|
|
135
136
|
BLOCK_DUMPER_MAX_ATTEMPTS = 3
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
CELERY_BEAT_SCHEDULE = {
|
|
140
|
+
"cleanup-old-tasks": {
|
|
141
|
+
"task": "abstract_block_dumper.cleanup_old_tasks",
|
|
142
|
+
"schedule": crontab(hour=2, minute=0), # Daily at 2 AM
|
|
143
|
+
"kwargs": {"days": 7}, # Customize retention period
|
|
144
|
+
},
|
|
145
|
+
}
|
|
@@ -6,8 +6,8 @@ from django.db import transaction
|
|
|
6
6
|
from django.db.models.query import QuerySet
|
|
7
7
|
from django.utils import timezone
|
|
8
8
|
|
|
9
|
+
import abstract_block_dumper._internal.services.utils as abd_utils
|
|
9
10
|
import abstract_block_dumper.models as abd_models
|
|
10
|
-
import abstract_block_dumper.services.utils as abd_utils
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_ready_to_retry_attempts() -> QuerySet[abd_models.TaskAttempt]:
|
|
@@ -76,7 +76,7 @@ def task_mark_as_success(task: abd_models.TaskAttempt, result_data: dict) -> Non
|
|
|
76
76
|
task.save()
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def task_mark_as_failed(task) -> None:
|
|
79
|
+
def task_mark_as_failed(task: abd_models.TaskAttempt) -> None:
|
|
80
80
|
DEFAULT_BLOCK_TASK_RETRY_BACKOFF = 1
|
|
81
81
|
MAX_RETRY_DELAY_MINUTES = 1440 # 24 hours max delay
|
|
82
82
|
|
|
@@ -98,7 +98,7 @@ def task_mark_as_failed(task) -> None:
|
|
|
98
98
|
task.save()
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def task_schedule_to_retry(task):
|
|
101
|
+
def task_schedule_to_retry(task: abd_models.TaskAttempt) -> None:
|
|
102
102
|
task.status = abd_models.TaskAttempt.Status.PENDING
|
|
103
103
|
task.save()
|
|
104
104
|
|
|
@@ -110,6 +110,7 @@ def task_create_or_get_pending(
|
|
|
110
110
|
) -> tuple[abd_models.TaskAttempt, bool]:
|
|
111
111
|
"""
|
|
112
112
|
Create or get a pending task attempt.
|
|
113
|
+
|
|
113
114
|
Returns (task, created) where created indicates if a new task was created.
|
|
114
115
|
|
|
115
116
|
For failed tasks that can retry:
|