abstract-block-dumper 0.0.5__tar.gz → 0.0.6__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.5 → abstract_block_dumper-0.0.6}/.gitignore +2 -1
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/CHANGELOG.md +4 -4
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/PKG-INFO +132 -54
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/README.md +130 -52
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/README.md +1 -1
- abstract_block_dumper-0.0.6/example_project/block_explorer/tasks.py +46 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/docker-compose.yml +1 -1
- abstract_block_dumper-0.0.6/example_project/example_project/celery.py +26 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/example_project/settings.py +10 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/noxfile.py +4 -1
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/pyproject.toml +2 -2
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/dal/django_dal.py +4 -3
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/dal/memory_registry.py +12 -20
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/discovery.py +1 -1
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/exceptions.py +1 -1
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/services/block_processor.py +15 -14
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/services/executor.py +2 -2
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/services/scheduler.py +9 -7
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/services/utils.py +3 -7
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/_version.py +2 -2
- abstract_block_dumper-0.0.5/src/abstract_block_dumper/management/commands/block_tasks.py → abstract_block_dumper-0.0.6/src/abstract_block_dumper/management/commands/block_tasks_v1.py +6 -3
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/models.py +1 -1
- abstract_block_dumper-0.0.6/src/abstract_block_dumper/v1/celery.py +53 -0
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/v1}/decorators.py +56 -39
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper → abstract_block_dumper-0.0.6/src/abstract_block_dumper/v1}/tasks.py +3 -3
- abstract_block_dumper-0.0.6/tests/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/conftest.py +2 -2
- abstract_block_dumper-0.0.6/tests/integration/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_block_processor.py +3 -3
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_concurrent_processing.py +6 -6
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_multi_arguments_tasks.py +4 -4
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_registered_celery_tasks.py +6 -7
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_scheduler.py +9 -9
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/integration/test_task_registration.py +4 -4
- abstract_block_dumper-0.0.6/tests/unit/test_celery_integration.py +17 -0
- abstract_block_dumper-0.0.6/tests/unit/test_decorator.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/uv.lock +1 -1
- abstract_block_dumper-0.0.5/example_project/block_explorer/tasks.py +0 -27
- abstract_block_dumper-0.0.5/example_project/example_project/celery.py +0 -12
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.cruft.json +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.github/dependabot.yml +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.github/workflows/ci.yml +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.github/workflows/publish.yml +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.pre-commit-config.yaml +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/.shellcheckrc +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/SECURITY.md +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/docs/3rd_party/cookiecutter-rt-pkg/CHANGELOG.md +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/.dockerignore +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/.gitignore +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/Dockerfile +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/admin.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/apps.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/management/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/management/commands/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/management/commands/create_admin.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/migrations/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/models.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/tests.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/block_explorer/views.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/example_project/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/example_project/asgi.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/example_project/urls.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/example_project/wsgi.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/main.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/manage.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/pyproject.toml +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/pytest.ini +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/example_project/uv.lock +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/__init__.py +0 -0
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper/dal → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper/management → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal/dal}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper/migrations → abstract_block_dumper-0.0.6/src/abstract_block_dumper/_internal/services}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/admin.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/apps.py +0 -0
- {abstract_block_dumper-0.0.5/src/abstract_block_dumper/services → abstract_block_dumper-0.0.6/src/abstract_block_dumper/management}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5/tests → abstract_block_dumper-0.0.6/src/abstract_block_dumper/management/commands}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/migrations/0001_initial.py +0 -0
- {abstract_block_dumper-0.0.5/tests/integration → abstract_block_dumper-0.0.6/src/abstract_block_dumper/migrations}/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/src/abstract_block_dumper/py.typed +0 -0
- /abstract_block_dumper-0.0.5/tests/unit/test_decorator.py → /abstract_block_dumper-0.0.6/src/abstract_block_dumper/v1/__init__.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/django_fixtures.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/fatories.py +0 -0
- {abstract_block_dumper-0.0.5 → abstract_block_dumper-0.0.6}/tests/settings.py +0 -0
|
@@ -9,22 +9,22 @@ upcoming release can be found in [changelog.d](changelog.d).
|
|
|
9
9
|
|
|
10
10
|
<!-- towncrier release notes start -->
|
|
11
11
|
|
|
12
|
-
## [0.0.
|
|
12
|
+
## [0.0.6](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.6) - 2025-11-26
|
|
13
13
|
|
|
14
14
|
No significant changes.
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
## [0.0.4](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.4) - 2025-
|
|
17
|
+
## [0.0.4](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.4) - 2025-11-17
|
|
18
18
|
|
|
19
19
|
No significant changes.
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
## [0.0.
|
|
22
|
+
## [0.0.5](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.5) - 2025-11-17
|
|
23
23
|
|
|
24
24
|
No significant changes.
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
## [0.0.2](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.2) - 2025-10-
|
|
27
|
+
## [0.0.2](https://github.com/bactensor/abstract-block-dumper/releases/tag/v0.0.2) - 2025-10-24
|
|
28
28
|
|
|
29
29
|
No significant changes.
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstract-block-dumper
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
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>
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Topic :: Software Development :: Libraries
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
17
|
Requires-Dist: bittensor>=9.10.1
|
|
18
|
-
Requires-Dist: celery>=5.
|
|
18
|
+
Requires-Dist: celery>=5.3
|
|
19
19
|
Requires-Dist: django<6.0,>=3.2
|
|
20
20
|
Requires-Dist: structlog>=25.4.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -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,31 @@ 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 celeryd_init
|
|
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
|
+
|
|
115
|
+
|
|
116
|
+
@celeryd_init.connect
|
|
117
|
+
def on_worker_init(**kwargs) -> None:
|
|
118
|
+
"""Load block tasks when worker initializes."""
|
|
119
|
+
from abstract_block_dumper.v1.celery import setup_celery_tasks
|
|
120
|
+
setup_celery_tasks()
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> **Important:** Without this step, Celery workers will not recognize your `@block_task` decorated functions, and you'll see "Received unregistered task" errors.
|
|
125
|
+
|
|
85
126
|
## Usage
|
|
86
127
|
|
|
87
128
|
### 1. Define Block Processing Tasks
|
|
@@ -93,7 +134,7 @@ Create block processing tasks in `tasks.py` or `block_tasks.py` file inside any
|
|
|
93
134
|
### 3. Start the Block Scheduler
|
|
94
135
|
Run the scheduler to start processing blocks:
|
|
95
136
|
```bash
|
|
96
|
-
$ python manage.py
|
|
137
|
+
$ python manage.py block_tasks_v1
|
|
97
138
|
```
|
|
98
139
|
|
|
99
140
|
This command will:
|
|
@@ -112,11 +153,11 @@ See examples below:
|
|
|
112
153
|
Use the `@block_task` decorator with lambda conditions to create block processing tasks:
|
|
113
154
|
|
|
114
155
|
```python
|
|
115
|
-
from abstract_block_dumper.decorators import block_task
|
|
156
|
+
from abstract_block_dumper.v1.decorators import block_task
|
|
116
157
|
|
|
117
158
|
|
|
118
159
|
# Process every block
|
|
119
|
-
@block_task
|
|
160
|
+
@block_task
|
|
120
161
|
def process_every_block(block_number: int):
|
|
121
162
|
print(f"Processing every block: {block_number}")
|
|
122
163
|
|
|
@@ -144,7 +185,7 @@ def process_multi_netuid_task(block_number: int, netuid: int):
|
|
|
144
185
|
The framework provides a maintenance task to clean up old task records and maintain database performance:
|
|
145
186
|
|
|
146
187
|
```python
|
|
147
|
-
from abstract_block_dumper.tasks import cleanup_old_tasks
|
|
188
|
+
from abstract_block_dumper.v1.tasks import cleanup_old_tasks
|
|
148
189
|
|
|
149
190
|
# Delete tasks older than 7 days (default)
|
|
150
191
|
cleanup_old_tasks.delay()
|
|
@@ -160,13 +201,13 @@ This task deletes all succeeded or unrecoverable failed tasks older than the spe
|
|
|
160
201
|
**Option 1: Manual Execution**
|
|
161
202
|
```bash
|
|
162
203
|
# Using Django shell
|
|
163
|
-
python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
204
|
+
python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
164
205
|
```
|
|
165
206
|
|
|
166
207
|
**Option 2: Cron Job (Recommended - once per day)**
|
|
167
208
|
```bash
|
|
168
209
|
# 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()"
|
|
210
|
+
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
211
|
```
|
|
171
212
|
|
|
172
213
|
**Option 3: Celery Beat (Automated Scheduling)**
|
|
@@ -210,55 +251,92 @@ BLOCK_DUMPER_MAX_ATTEMPTS = 3 # maximum retry attempts
|
|
|
210
251
|
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 1440 # maximum retry delay (24 hours)
|
|
211
252
|
```
|
|
212
253
|
|
|
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
|
-
-
|
|
254
|
+
## Configuration Options Reference
|
|
255
|
+
|
|
256
|
+
### `BITTENSOR_NETWORK`
|
|
257
|
+
- **Type:** `str`
|
|
258
|
+
- **Default:** `'finney'`
|
|
259
|
+
- **Description:** Specifies which [Bittensor network](https://docs.learnbittensor.org/concepts/bittensor-networks) to connect to
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### `BLOCK_DUMPER_START_FROM_BLOCK`
|
|
264
|
+
- **Type:** `str | int | None`
|
|
265
|
+
- **Default:** `None`
|
|
266
|
+
- **Valid Range:** `None`, `'current'`, or any positive integer
|
|
267
|
+
- **Description:** Determines the starting block for processing when the scheduler first runs
|
|
268
|
+
- `None` → Resume from the last processed block stored in database
|
|
269
|
+
- `'current'` → Start from the current blockchain block (skips historical blocks)
|
|
270
|
+
- Integer → Start from a specific block number (e.g., `1000000`)
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
BLOCK_DUMPER_START_FROM_BLOCK = 'current'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
> **Performance Impact:** Starting from historical blocks may require significant processing time
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
### `BLOCK_DUMPER_POLL_INTERVAL`
|
|
281
|
+
- **Type:** `int`
|
|
282
|
+
- **Default:** `1`
|
|
283
|
+
- **Valid Range:** `1` to `3600` (seconds)
|
|
284
|
+
- **Description:** Seconds to wait between checking for new blocks
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
BLOCK_DUMPER_POLL_INTERVAL = 5
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
> **Performance Impact:**
|
|
291
|
+
> - Lower values (1-2s): Near real-time processing, higher CPU/network usage
|
|
292
|
+
> - Higher values (10-60s): Reduced load but delayed processing
|
|
293
|
+
> - Very low values (<1s): May cause rate limiting
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### `BLOCK_DUMPER_MAX_ATTEMPTS`
|
|
298
|
+
- **Type:** `int`
|
|
299
|
+
- **Default:** `3`
|
|
300
|
+
- **Valid Range:** `1` to `10`
|
|
301
|
+
- **Description:** Maximum number of attempts to retry a failed task before giving up
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
BLOCK_DUMPER_MAX_ATTEMPTS = 5
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
> **Performance Impact:** Higher values increase resilience but may delay failure detection
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### `BLOCK_TASK_RETRY_BACKOFF`
|
|
312
|
+
- **Type:** `int`
|
|
313
|
+
- **Default:** `1`
|
|
314
|
+
- **Valid Range:** `1` to `60` (minutes)
|
|
315
|
+
- **Description:** Base number of minutes for exponential backoff retry delays
|
|
316
|
+
- **Calculation:** Actual delay = `backoff ** attempt_count` minutes
|
|
252
317
|
- Attempt 1: 2¹ = 2 minutes
|
|
253
|
-
- Attempt 2: 2² = 4 minutes
|
|
318
|
+
- Attempt 2: 2² = 4 minutes
|
|
254
319
|
- Attempt 3: 2³ = 8 minutes
|
|
255
|
-
- **Performance Impact**: Lower values retry faster but may overwhelm failing services
|
|
256
320
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
321
|
+
```python
|
|
322
|
+
BLOCK_TASK_RETRY_BACKOFF = 2
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
> **Performance Impact:** Lower values retry faster but may overwhelm failing services
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `BLOCK_TASK_MAX_RETRY_DELAY_MINUTES`
|
|
330
|
+
- **Type:** `int`
|
|
331
|
+
- **Default:** `1440` (24 hours)
|
|
332
|
+
- **Valid Range:** `1` to `10080` (1 minute to 1 week)
|
|
333
|
+
- **Description:** Maximum delay (in minutes) between retry attempts, caps exponential backoff
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 720 # 12 hours max
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> **Performance Impact:** Prevents extremely long delays while maintaining backoff benefits
|
|
262
340
|
|
|
263
341
|
|
|
264
342
|
## 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,31 @@ 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 celeryd_init
|
|
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
|
+
|
|
93
|
+
|
|
94
|
+
@celeryd_init.connect
|
|
95
|
+
def on_worker_init(**kwargs) -> None:
|
|
96
|
+
"""Load block tasks when worker initializes."""
|
|
97
|
+
from abstract_block_dumper.v1.celery import setup_celery_tasks
|
|
98
|
+
setup_celery_tasks()
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> **Important:** Without this step, Celery workers will not recognize your `@block_task` decorated functions, and you'll see "Received unregistered task" errors.
|
|
103
|
+
|
|
63
104
|
## Usage
|
|
64
105
|
|
|
65
106
|
### 1. Define Block Processing Tasks
|
|
@@ -71,7 +112,7 @@ Create block processing tasks in `tasks.py` or `block_tasks.py` file inside any
|
|
|
71
112
|
### 3. Start the Block Scheduler
|
|
72
113
|
Run the scheduler to start processing blocks:
|
|
73
114
|
```bash
|
|
74
|
-
$ python manage.py
|
|
115
|
+
$ python manage.py block_tasks_v1
|
|
75
116
|
```
|
|
76
117
|
|
|
77
118
|
This command will:
|
|
@@ -90,11 +131,11 @@ See examples below:
|
|
|
90
131
|
Use the `@block_task` decorator with lambda conditions to create block processing tasks:
|
|
91
132
|
|
|
92
133
|
```python
|
|
93
|
-
from abstract_block_dumper.decorators import block_task
|
|
134
|
+
from abstract_block_dumper.v1.decorators import block_task
|
|
94
135
|
|
|
95
136
|
|
|
96
137
|
# Process every block
|
|
97
|
-
@block_task
|
|
138
|
+
@block_task
|
|
98
139
|
def process_every_block(block_number: int):
|
|
99
140
|
print(f"Processing every block: {block_number}")
|
|
100
141
|
|
|
@@ -122,7 +163,7 @@ def process_multi_netuid_task(block_number: int, netuid: int):
|
|
|
122
163
|
The framework provides a maintenance task to clean up old task records and maintain database performance:
|
|
123
164
|
|
|
124
165
|
```python
|
|
125
|
-
from abstract_block_dumper.tasks import cleanup_old_tasks
|
|
166
|
+
from abstract_block_dumper.v1.tasks import cleanup_old_tasks
|
|
126
167
|
|
|
127
168
|
# Delete tasks older than 7 days (default)
|
|
128
169
|
cleanup_old_tasks.delay()
|
|
@@ -138,13 +179,13 @@ This task deletes all succeeded or unrecoverable failed tasks older than the spe
|
|
|
138
179
|
**Option 1: Manual Execution**
|
|
139
180
|
```bash
|
|
140
181
|
# Using Django shell
|
|
141
|
-
python manage.py shell -c "from abstract_block_dumper.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
182
|
+
python manage.py shell -c "from abstract_block_dumper.v1.tasks import cleanup_old_tasks; cleanup_old_tasks.delay()"
|
|
142
183
|
```
|
|
143
184
|
|
|
144
185
|
**Option 2: Cron Job (Recommended - once per day)**
|
|
145
186
|
```bash
|
|
146
187
|
# 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()"
|
|
188
|
+
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
189
|
```
|
|
149
190
|
|
|
150
191
|
**Option 3: Celery Beat (Automated Scheduling)**
|
|
@@ -188,55 +229,92 @@ BLOCK_DUMPER_MAX_ATTEMPTS = 3 # maximum retry attempts
|
|
|
188
229
|
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 1440 # maximum retry delay (24 hours)
|
|
189
230
|
```
|
|
190
231
|
|
|
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
|
-
-
|
|
232
|
+
## Configuration Options Reference
|
|
233
|
+
|
|
234
|
+
### `BITTENSOR_NETWORK`
|
|
235
|
+
- **Type:** `str`
|
|
236
|
+
- **Default:** `'finney'`
|
|
237
|
+
- **Description:** Specifies which [Bittensor network](https://docs.learnbittensor.org/concepts/bittensor-networks) to connect to
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### `BLOCK_DUMPER_START_FROM_BLOCK`
|
|
242
|
+
- **Type:** `str | int | None`
|
|
243
|
+
- **Default:** `None`
|
|
244
|
+
- **Valid Range:** `None`, `'current'`, or any positive integer
|
|
245
|
+
- **Description:** Determines the starting block for processing when the scheduler first runs
|
|
246
|
+
- `None` → Resume from the last processed block stored in database
|
|
247
|
+
- `'current'` → Start from the current blockchain block (skips historical blocks)
|
|
248
|
+
- Integer → Start from a specific block number (e.g., `1000000`)
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
BLOCK_DUMPER_START_FROM_BLOCK = 'current'
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
> **Performance Impact:** Starting from historical blocks may require significant processing time
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### `BLOCK_DUMPER_POLL_INTERVAL`
|
|
259
|
+
- **Type:** `int`
|
|
260
|
+
- **Default:** `1`
|
|
261
|
+
- **Valid Range:** `1` to `3600` (seconds)
|
|
262
|
+
- **Description:** Seconds to wait between checking for new blocks
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
BLOCK_DUMPER_POLL_INTERVAL = 5
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
> **Performance Impact:**
|
|
269
|
+
> - Lower values (1-2s): Near real-time processing, higher CPU/network usage
|
|
270
|
+
> - Higher values (10-60s): Reduced load but delayed processing
|
|
271
|
+
> - Very low values (<1s): May cause rate limiting
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### `BLOCK_DUMPER_MAX_ATTEMPTS`
|
|
276
|
+
- **Type:** `int`
|
|
277
|
+
- **Default:** `3`
|
|
278
|
+
- **Valid Range:** `1` to `10`
|
|
279
|
+
- **Description:** Maximum number of attempts to retry a failed task before giving up
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
BLOCK_DUMPER_MAX_ATTEMPTS = 5
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
> **Performance Impact:** Higher values increase resilience but may delay failure detection
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### `BLOCK_TASK_RETRY_BACKOFF`
|
|
290
|
+
- **Type:** `int`
|
|
291
|
+
- **Default:** `1`
|
|
292
|
+
- **Valid Range:** `1` to `60` (minutes)
|
|
293
|
+
- **Description:** Base number of minutes for exponential backoff retry delays
|
|
294
|
+
- **Calculation:** Actual delay = `backoff ** attempt_count` minutes
|
|
230
295
|
- Attempt 1: 2¹ = 2 minutes
|
|
231
|
-
- Attempt 2: 2² = 4 minutes
|
|
296
|
+
- Attempt 2: 2² = 4 minutes
|
|
232
297
|
- Attempt 3: 2³ = 8 minutes
|
|
233
|
-
- **Performance Impact**: Lower values retry faster but may overwhelm failing services
|
|
234
298
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
299
|
+
```python
|
|
300
|
+
BLOCK_TASK_RETRY_BACKOFF = 2
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
> **Performance Impact:** Lower values retry faster but may overwhelm failing services
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### `BLOCK_TASK_MAX_RETRY_DELAY_MINUTES`
|
|
308
|
+
- **Type:** `int`
|
|
309
|
+
- **Default:** `1440` (24 hours)
|
|
310
|
+
- **Valid Range:** `1` to `10080` (1 minute to 1 week)
|
|
311
|
+
- **Description:** Maximum delay (in minutes) between retry attempts, caps exponential backoff
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
BLOCK_TASK_MAX_RETRY_DELAY_MINUTES = 720 # 12 hours max
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
> **Performance Impact:** Prevents extremely long delays while maintaining backoff benefits
|
|
240
318
|
|
|
241
319
|
|
|
242
320
|
## 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,46 @@
|
|
|
1
|
+
from abstract_block_dumper.v1.decorators import block_task
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@block_task
|
|
5
|
+
def process_every_block(block_number: int, netuid: int | None = None) -> str:
|
|
6
|
+
"""
|
|
7
|
+
Example task that processes every block.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
block_number (int): The block number to process.
|
|
11
|
+
netuid (int | None): The NetUID to process, if applicable.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
str: A message indicating the block has been processed.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
return f"Processed block {block_number} for NetUID {netuid}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@block_task(
|
|
21
|
+
backfilling_lookback=100,
|
|
22
|
+
)
|
|
23
|
+
def backfill_previous_100_blocks(block_number: int, netuid: int | None = None) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Example task that backfills the previous 100 blocks.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
block_number (int): The block number to process.
|
|
29
|
+
netuid (int | None): The NetUID to process, if applicable.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: A message indicating the block has been processed.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
return f"Processed block {block_number} for NetUID {netuid}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@block_task(
|
|
39
|
+
condition=lambda bn, netuid: (bn + netuid) % 50 == 0,
|
|
40
|
+
args=[{"netuid": i} for i in range(10, 15)], # All subnets
|
|
41
|
+
backfilling_lookback=1000,
|
|
42
|
+
celery_kwargs={"retry": True},
|
|
43
|
+
)
|
|
44
|
+
def subnet_analysis(block_number, netuid):
|
|
45
|
+
# Analyze subnet data
|
|
46
|
+
return f"Processed block {block_number} for NetUID {netuid}"
|
|
@@ -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
|
+
}
|
|
@@ -18,6 +18,7 @@ MAIN_BRANCH_NAME = "master"
|
|
|
18
18
|
PYTHON_VERSIONS = ["3.11", "3.12"]
|
|
19
19
|
PYTHON_DEFAULT_VERSION = PYTHON_VERSIONS[-1]
|
|
20
20
|
DJANGO_VERSIONS = ["3.2", "4.2"]
|
|
21
|
+
CELERY_VERSIONS = ["5.3", "5.4", "5.5"]
|
|
21
22
|
DEMO_APP_DIR = ROOT / "demo"
|
|
22
23
|
|
|
23
24
|
nox.options.default_venv_backend = "venv"
|
|
@@ -152,9 +153,11 @@ def lint(session):
|
|
|
152
153
|
|
|
153
154
|
@nox.session(python=PYTHON_VERSIONS, tags=["test", "check"])
|
|
154
155
|
@nox.parametrize("django", DJANGO_VERSIONS)
|
|
155
|
-
|
|
156
|
+
@nox.parametrize("celery", CELERY_VERSIONS)
|
|
157
|
+
def test(session, django: str, celery: str):
|
|
156
158
|
install(session, "test", editable=True)
|
|
157
159
|
session.run("pip", "install", f"django~={django}.0")
|
|
160
|
+
session.run("pip", "install", f"celery~={celery}.0")
|
|
158
161
|
session.run("pytest", "-vv", "-n", "auto", *session.posargs)
|
|
159
162
|
|
|
160
163
|
|