django-melnottam 0.1.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.
- django_melnottam-0.1.0/MANIFEST.in +9 -0
- django_melnottam-0.1.0/PKG-INFO +286 -0
- django_melnottam-0.1.0/README.md +250 -0
- django_melnottam-0.1.0/pyproject.toml +147 -0
- django_melnottam-0.1.0/setup.cfg +4 -0
- django_melnottam-0.1.0/src/django_melnottam/__init__.py +0 -0
- django_melnottam-0.1.0/src/django_melnottam/cli.py +101 -0
- django_melnottam-0.1.0/src/django_melnottam/config.py +61 -0
- django_melnottam-0.1.0/src/django_melnottam/exceptions.py +40 -0
- django_melnottam-0.1.0/src/django_melnottam/logging_utils.py +28 -0
- django_melnottam-0.1.0/src/django_melnottam/manager.py +294 -0
- django_melnottam-0.1.0/src/django_melnottam/prodserver_backends/__init__.py +3 -0
- django_melnottam-0.1.0/src/django_melnottam/prodserver_backends/subinterpreter.py +57 -0
- django_melnottam-0.1.0/src/django_melnottam/tasks.py +317 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/PKG-INFO +286 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/SOURCES.txt +20 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/dependency_links.txt +1 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/entry_points.txt +2 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/requires.txt +12 -0
- django_melnottam-0.1.0/src/django_melnottam.egg-info/top_level.txt +1 -0
- django_melnottam-0.1.0/tests/test_integration.py +82 -0
- django_melnottam-0.1.0/tests/test_manager.py +92 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-melnottam
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for managing Django application workers using InterpreterPoolExecutor
|
|
5
|
+
Author-email: melhin <melhin@duck.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://codeberg.org/melhin/django-melnottam
|
|
8
|
+
Project-URL: Repository, https://codeberg.org/melhin/django-melnottam
|
|
9
|
+
Project-URL: Documentation, https://codeberg.org/melhin/django-melnottam
|
|
10
|
+
Project-URL: Issues, https://codeberg.org/melhin/django-melnottam/issues
|
|
11
|
+
Keywords: django,workers,interpreter-pool,concurrent,subinterpreters,hypercorn,asgi,wsgi
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 6.0
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.14
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: Django<7.0,>=6.0
|
|
26
|
+
Requires-Dist: django-prodserver>=2.4.0
|
|
27
|
+
Requires-Dist: django-tasks-db>=0.12.0
|
|
28
|
+
Requires-Dist: hypercorn>=0.18.0
|
|
29
|
+
Requires-Dist: rich>=15.0.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# Django Melnottam
|
|
38
|
+
|
|
39
|
+
⚠️ **EXPERIMENTAL** - This library is still in early development. APIs are subject to change without notice. Use at your own risk and with caution in production environments.
|
|
40
|
+
|
|
41
|
+
A Python package for managing Django application workers using `InterpreterPoolExecutor` from Python 3.14+. This package provides a clean abstraction for running web and task workers in isolated Python interpreters.
|
|
42
|
+
|
|
43
|
+
## Overview
|
|
44
|
+
|
|
45
|
+
`django-melnottam` manages multiple worker processes using Python 3.14's `concurrent.futures.InterpreterPoolExecutor`. It provides:
|
|
46
|
+
|
|
47
|
+
- **Web Workers**: ASGI/WSGI application servers using Hypercorn
|
|
48
|
+
- **Task Workers**: Django task queue workers using `django-tasks-db`
|
|
49
|
+
- **Isolated Execution**: Each worker runs in its own Python interpreter, avoiding GIL contention
|
|
50
|
+
- **Graceful Shutdown**: Coordinated shutdown with timeout handling
|
|
51
|
+
- **Signal Management**: Proper SIGINT handling and cleanup
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- ✅ Multiple web and task workers managed in a single pool
|
|
56
|
+
- ✅ Shared shutdown coordination via inter-interpreter queues
|
|
57
|
+
- ✅ Configurable worker counts and binding addresses
|
|
58
|
+
- ✅ Django settings module support
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Python 3.14+
|
|
63
|
+
- Django 6.0+
|
|
64
|
+
- `django-tasks-db >= 0.12.0`
|
|
65
|
+
- `hypercorn >= 0.18.0`
|
|
66
|
+
- `rich >= 15.0.0`
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
### From GitHub
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install git+https://github.com/yourusername/django-subinterpreter-workers.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
### Command Line Interface
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
django-melnottam-cli <application> [options]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Arguments
|
|
86
|
+
|
|
87
|
+
- `application`: Path to your WSGI application (e.g., `myapp.wsgi:application`)
|
|
88
|
+
|
|
89
|
+
#### Options
|
|
90
|
+
|
|
91
|
+
- `-w, --workers`: Number of web workers (default: 2)
|
|
92
|
+
- `-t, --task-workers`: Number of task workers (default: 1)
|
|
93
|
+
- `-b, --bind`: Bind address for web server (default: `0.0.0.0:9001`)
|
|
94
|
+
- `-s, --settings`: Django settings module (default: from `DJANGO_SETTINGS_MODULE` env var)
|
|
95
|
+
- `-v, --verbose`: Enable debug logging
|
|
96
|
+
|
|
97
|
+
#### Example
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
django-melnottam-cli myapp.wsgi:application \
|
|
101
|
+
--workers 4 \
|
|
102
|
+
--task-workers 2 \
|
|
103
|
+
--bind 0.0.0.0:8000 \
|
|
104
|
+
--settings myapp.settings.production
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Programmatic Usage
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from django_melnottam.manager import run_application
|
|
111
|
+
|
|
112
|
+
run_application(
|
|
113
|
+
app_path="myapp.wsgi:application",
|
|
114
|
+
workers=4,
|
|
115
|
+
task_workers=2,
|
|
116
|
+
bind="0.0.0.0:8000",
|
|
117
|
+
django_settings_module="myapp.settings"
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Using the InterpreterPoolManager Directly
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from django_melnottam.manager import InterpreterPoolManager
|
|
125
|
+
from django_melnottam.config import WebWorkerConfig, TaskWorkerConfig
|
|
126
|
+
|
|
127
|
+
# Create manager with total worker count
|
|
128
|
+
pool = InterpreterPoolManager(max_workers=6)
|
|
129
|
+
|
|
130
|
+
# Configure and start web workers
|
|
131
|
+
web_config = WebWorkerConfig(
|
|
132
|
+
worker_number=1,
|
|
133
|
+
log_level=logging.INFO,
|
|
134
|
+
application_path="myapp.wsgi:application",
|
|
135
|
+
workers=4,
|
|
136
|
+
bind="0.0.0.0:8000"
|
|
137
|
+
)
|
|
138
|
+
pool.start_worker(web_config)
|
|
139
|
+
|
|
140
|
+
# Configure and start task workers
|
|
141
|
+
task_config = TaskWorkerConfig(
|
|
142
|
+
worker_number=2,
|
|
143
|
+
log_level=logging.INFO
|
|
144
|
+
)
|
|
145
|
+
pool.start_worker(task_config)
|
|
146
|
+
|
|
147
|
+
# Gracefully shutdown
|
|
148
|
+
pool.shutdown(timeout=10.0)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Preferred Deployment
|
|
152
|
+
|
|
153
|
+
This library includes a built-in `django-prodserver` backend that provides the recommended way to deploy your Django application with web and task workers.
|
|
154
|
+
|
|
155
|
+
### Production Deployment Command
|
|
156
|
+
|
|
157
|
+
To use the preferred deployment method with `django-prodserver`, run the following command with environment variable substitution:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
BIND_ADDRESS="0.0.0.0:$PORT" WORKERS="$WORKERS" TASK_WORKERS="$INSTANCE_TASK_WORKERS" python manage.py prodserver web-with-task-worker
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Configuration in Django Settings
|
|
164
|
+
|
|
165
|
+
Ensure your `settings.py` includes the production process configuration:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from django_melnottam.config import PositiveIntegerValue, Value
|
|
169
|
+
|
|
170
|
+
INSTALLED_APPS = [
|
|
171
|
+
# ...
|
|
172
|
+
"django_tasks_db",
|
|
173
|
+
"django_prodserver",
|
|
174
|
+
# ...
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
TASKS = {
|
|
178
|
+
"default": {
|
|
179
|
+
"BACKEND": "django_tasks_db.DatabaseBackend",
|
|
180
|
+
"QUEUES": ["default"],
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Production process configuration with command-line parameter support
|
|
185
|
+
|
|
186
|
+
PRODUCTION_PROCESSES = {
|
|
187
|
+
"web-with-task-worker": {
|
|
188
|
+
"BACKEND": "django_melnottam.prodserver_backends.subinterpreter.SubinterpreterWorkerServer",
|
|
189
|
+
"ARGS": {
|
|
190
|
+
"app_path": "myapp.wsgi:application",
|
|
191
|
+
"django_settings_module": "myapp.settings",
|
|
192
|
+
"workers": WORKERS,
|
|
193
|
+
"task_workers": TASK_WORKERS,
|
|
194
|
+
"bind": BIND_ADDRESS,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Environment Variables
|
|
201
|
+
|
|
202
|
+
- `PORT`: The port to bind to (e.g., `8000`)
|
|
203
|
+
- `WORKERS`: Number of web workers (e.g., `4`)
|
|
204
|
+
- `INSTANCE_TASK_WORKERS`: Number of task workers (e.g., `2`)
|
|
205
|
+
|
|
206
|
+
### Why Use This Backend?
|
|
207
|
+
|
|
208
|
+
The `SubinterpreterWorkerServer` backend provides:
|
|
209
|
+
- Built-in support for multiple web and task workers
|
|
210
|
+
- Automatic worker management and coordination
|
|
211
|
+
- Graceful shutdown handling
|
|
212
|
+
- Integration with `django-prodserver` for seamless deployment
|
|
213
|
+
|
|
214
|
+
## Configuration
|
|
215
|
+
|
|
216
|
+
### Django Settings
|
|
217
|
+
|
|
218
|
+
Ensure your Django settings module is properly configured with:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# settings.py
|
|
222
|
+
TASKS = {
|
|
223
|
+
"default": {
|
|
224
|
+
"BACKEND": "django_tasks_db.InMemoryBackend",
|
|
225
|
+
# or use your preferred backend
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Environment Variables
|
|
231
|
+
|
|
232
|
+
- `DJANGO_SETTINGS_MODULE`: Path to your Django settings module (required if not provided via CLI)
|
|
233
|
+
|
|
234
|
+
## Architecture
|
|
235
|
+
|
|
236
|
+
### Worker Types
|
|
237
|
+
|
|
238
|
+
#### Web Workers
|
|
239
|
+
- Run Hypercorn ASGI server
|
|
240
|
+
- Each worker binds to the same port (handled via socket duplication)
|
|
241
|
+
- Manages HTTP requests
|
|
242
|
+
- Shutdown triggered by stop signal or exception
|
|
243
|
+
|
|
244
|
+
#### Task Workers
|
|
245
|
+
- Run `django-tasks-db` worker
|
|
246
|
+
- Process background tasks from the task queue
|
|
247
|
+
- Single worker processes tasks sequentially
|
|
248
|
+
|
|
249
|
+
### Communication
|
|
250
|
+
|
|
251
|
+
Workers communicate via `concurrent.interpreters.Queue`:
|
|
252
|
+
|
|
253
|
+
- **Shutdown Queue**: Each worker monitors its shutdown queue for stop signals
|
|
254
|
+
- **Parent Shutdown Queue**: Workers can signal the parent to shutdown
|
|
255
|
+
- **Worker Queue**: Reserved for future inter-worker communication
|
|
256
|
+
|
|
257
|
+
### Interpreter Pool
|
|
258
|
+
|
|
259
|
+
The `InterpreterPoolExecutor` manages a pool of Python interpreters:
|
|
260
|
+
|
|
261
|
+
- Each submitted task runs in its own interpreter
|
|
262
|
+
- Interpreters are created on-demand up to `max_workers`
|
|
263
|
+
- No shared state between workers (strong isolation)
|
|
264
|
+
- GIL is not contended between workers
|
|
265
|
+
|
|
266
|
+
## Shutdown Behavior
|
|
267
|
+
|
|
268
|
+
1. SIGINT signal received → initiates graceful shutdown
|
|
269
|
+
2. Stop signal sent to all workers via shutdown queues
|
|
270
|
+
3. Workers begin cleanup (close sockets, flush logs)
|
|
271
|
+
4. Parent waits for worker futures with timeout
|
|
272
|
+
5. InterpreterPoolExecutor shuts down
|
|
273
|
+
6. Subinterpreters are cleaned up
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## Exceptions
|
|
277
|
+
|
|
278
|
+
The package provides custom exceptions for error handling:
|
|
279
|
+
|
|
280
|
+
- `WorkerException`: Base exception
|
|
281
|
+
- `WorkerInitializationError`: Worker failed to start
|
|
282
|
+
- `WorkerShutdownError`: Worker shutdown failed
|
|
283
|
+
- `ConfigurationError`: Invalid configuration
|
|
284
|
+
- `HookExecutionError`: Hook execution failed
|
|
285
|
+
- `TaskExecutionError`: Task execution failed
|
|
286
|
+
- `PoolExecutionError`: Pool-level error
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Django Melnottam
|
|
2
|
+
|
|
3
|
+
⚠️ **EXPERIMENTAL** - This library is still in early development. APIs are subject to change without notice. Use at your own risk and with caution in production environments.
|
|
4
|
+
|
|
5
|
+
A Python package for managing Django application workers using `InterpreterPoolExecutor` from Python 3.14+. This package provides a clean abstraction for running web and task workers in isolated Python interpreters.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`django-melnottam` manages multiple worker processes using Python 3.14's `concurrent.futures.InterpreterPoolExecutor`. It provides:
|
|
10
|
+
|
|
11
|
+
- **Web Workers**: ASGI/WSGI application servers using Hypercorn
|
|
12
|
+
- **Task Workers**: Django task queue workers using `django-tasks-db`
|
|
13
|
+
- **Isolated Execution**: Each worker runs in its own Python interpreter, avoiding GIL contention
|
|
14
|
+
- **Graceful Shutdown**: Coordinated shutdown with timeout handling
|
|
15
|
+
- **Signal Management**: Proper SIGINT handling and cleanup
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- ✅ Multiple web and task workers managed in a single pool
|
|
20
|
+
- ✅ Shared shutdown coordination via inter-interpreter queues
|
|
21
|
+
- ✅ Configurable worker counts and binding addresses
|
|
22
|
+
- ✅ Django settings module support
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.14+
|
|
27
|
+
- Django 6.0+
|
|
28
|
+
- `django-tasks-db >= 0.12.0`
|
|
29
|
+
- `hypercorn >= 0.18.0`
|
|
30
|
+
- `rich >= 15.0.0`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### From GitHub
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install git+https://github.com/yourusername/django-subinterpreter-workers.git
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Command Line Interface
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
django-melnottam-cli <application> [options]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Arguments
|
|
50
|
+
|
|
51
|
+
- `application`: Path to your WSGI application (e.g., `myapp.wsgi:application`)
|
|
52
|
+
|
|
53
|
+
#### Options
|
|
54
|
+
|
|
55
|
+
- `-w, --workers`: Number of web workers (default: 2)
|
|
56
|
+
- `-t, --task-workers`: Number of task workers (default: 1)
|
|
57
|
+
- `-b, --bind`: Bind address for web server (default: `0.0.0.0:9001`)
|
|
58
|
+
- `-s, --settings`: Django settings module (default: from `DJANGO_SETTINGS_MODULE` env var)
|
|
59
|
+
- `-v, --verbose`: Enable debug logging
|
|
60
|
+
|
|
61
|
+
#### Example
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
django-melnottam-cli myapp.wsgi:application \
|
|
65
|
+
--workers 4 \
|
|
66
|
+
--task-workers 2 \
|
|
67
|
+
--bind 0.0.0.0:8000 \
|
|
68
|
+
--settings myapp.settings.production
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Programmatic Usage
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from django_melnottam.manager import run_application
|
|
75
|
+
|
|
76
|
+
run_application(
|
|
77
|
+
app_path="myapp.wsgi:application",
|
|
78
|
+
workers=4,
|
|
79
|
+
task_workers=2,
|
|
80
|
+
bind="0.0.0.0:8000",
|
|
81
|
+
django_settings_module="myapp.settings"
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Using the InterpreterPoolManager Directly
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from django_melnottam.manager import InterpreterPoolManager
|
|
89
|
+
from django_melnottam.config import WebWorkerConfig, TaskWorkerConfig
|
|
90
|
+
|
|
91
|
+
# Create manager with total worker count
|
|
92
|
+
pool = InterpreterPoolManager(max_workers=6)
|
|
93
|
+
|
|
94
|
+
# Configure and start web workers
|
|
95
|
+
web_config = WebWorkerConfig(
|
|
96
|
+
worker_number=1,
|
|
97
|
+
log_level=logging.INFO,
|
|
98
|
+
application_path="myapp.wsgi:application",
|
|
99
|
+
workers=4,
|
|
100
|
+
bind="0.0.0.0:8000"
|
|
101
|
+
)
|
|
102
|
+
pool.start_worker(web_config)
|
|
103
|
+
|
|
104
|
+
# Configure and start task workers
|
|
105
|
+
task_config = TaskWorkerConfig(
|
|
106
|
+
worker_number=2,
|
|
107
|
+
log_level=logging.INFO
|
|
108
|
+
)
|
|
109
|
+
pool.start_worker(task_config)
|
|
110
|
+
|
|
111
|
+
# Gracefully shutdown
|
|
112
|
+
pool.shutdown(timeout=10.0)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Preferred Deployment
|
|
116
|
+
|
|
117
|
+
This library includes a built-in `django-prodserver` backend that provides the recommended way to deploy your Django application with web and task workers.
|
|
118
|
+
|
|
119
|
+
### Production Deployment Command
|
|
120
|
+
|
|
121
|
+
To use the preferred deployment method with `django-prodserver`, run the following command with environment variable substitution:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
BIND_ADDRESS="0.0.0.0:$PORT" WORKERS="$WORKERS" TASK_WORKERS="$INSTANCE_TASK_WORKERS" python manage.py prodserver web-with-task-worker
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Configuration in Django Settings
|
|
128
|
+
|
|
129
|
+
Ensure your `settings.py` includes the production process configuration:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from django_melnottam.config import PositiveIntegerValue, Value
|
|
133
|
+
|
|
134
|
+
INSTALLED_APPS = [
|
|
135
|
+
# ...
|
|
136
|
+
"django_tasks_db",
|
|
137
|
+
"django_prodserver",
|
|
138
|
+
# ...
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
TASKS = {
|
|
142
|
+
"default": {
|
|
143
|
+
"BACKEND": "django_tasks_db.DatabaseBackend",
|
|
144
|
+
"QUEUES": ["default"],
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Production process configuration with command-line parameter support
|
|
149
|
+
|
|
150
|
+
PRODUCTION_PROCESSES = {
|
|
151
|
+
"web-with-task-worker": {
|
|
152
|
+
"BACKEND": "django_melnottam.prodserver_backends.subinterpreter.SubinterpreterWorkerServer",
|
|
153
|
+
"ARGS": {
|
|
154
|
+
"app_path": "myapp.wsgi:application",
|
|
155
|
+
"django_settings_module": "myapp.settings",
|
|
156
|
+
"workers": WORKERS,
|
|
157
|
+
"task_workers": TASK_WORKERS,
|
|
158
|
+
"bind": BIND_ADDRESS,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Environment Variables
|
|
165
|
+
|
|
166
|
+
- `PORT`: The port to bind to (e.g., `8000`)
|
|
167
|
+
- `WORKERS`: Number of web workers (e.g., `4`)
|
|
168
|
+
- `INSTANCE_TASK_WORKERS`: Number of task workers (e.g., `2`)
|
|
169
|
+
|
|
170
|
+
### Why Use This Backend?
|
|
171
|
+
|
|
172
|
+
The `SubinterpreterWorkerServer` backend provides:
|
|
173
|
+
- Built-in support for multiple web and task workers
|
|
174
|
+
- Automatic worker management and coordination
|
|
175
|
+
- Graceful shutdown handling
|
|
176
|
+
- Integration with `django-prodserver` for seamless deployment
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
|
|
180
|
+
### Django Settings
|
|
181
|
+
|
|
182
|
+
Ensure your Django settings module is properly configured with:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# settings.py
|
|
186
|
+
TASKS = {
|
|
187
|
+
"default": {
|
|
188
|
+
"BACKEND": "django_tasks_db.InMemoryBackend",
|
|
189
|
+
# or use your preferred backend
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Environment Variables
|
|
195
|
+
|
|
196
|
+
- `DJANGO_SETTINGS_MODULE`: Path to your Django settings module (required if not provided via CLI)
|
|
197
|
+
|
|
198
|
+
## Architecture
|
|
199
|
+
|
|
200
|
+
### Worker Types
|
|
201
|
+
|
|
202
|
+
#### Web Workers
|
|
203
|
+
- Run Hypercorn ASGI server
|
|
204
|
+
- Each worker binds to the same port (handled via socket duplication)
|
|
205
|
+
- Manages HTTP requests
|
|
206
|
+
- Shutdown triggered by stop signal or exception
|
|
207
|
+
|
|
208
|
+
#### Task Workers
|
|
209
|
+
- Run `django-tasks-db` worker
|
|
210
|
+
- Process background tasks from the task queue
|
|
211
|
+
- Single worker processes tasks sequentially
|
|
212
|
+
|
|
213
|
+
### Communication
|
|
214
|
+
|
|
215
|
+
Workers communicate via `concurrent.interpreters.Queue`:
|
|
216
|
+
|
|
217
|
+
- **Shutdown Queue**: Each worker monitors its shutdown queue for stop signals
|
|
218
|
+
- **Parent Shutdown Queue**: Workers can signal the parent to shutdown
|
|
219
|
+
- **Worker Queue**: Reserved for future inter-worker communication
|
|
220
|
+
|
|
221
|
+
### Interpreter Pool
|
|
222
|
+
|
|
223
|
+
The `InterpreterPoolExecutor` manages a pool of Python interpreters:
|
|
224
|
+
|
|
225
|
+
- Each submitted task runs in its own interpreter
|
|
226
|
+
- Interpreters are created on-demand up to `max_workers`
|
|
227
|
+
- No shared state between workers (strong isolation)
|
|
228
|
+
- GIL is not contended between workers
|
|
229
|
+
|
|
230
|
+
## Shutdown Behavior
|
|
231
|
+
|
|
232
|
+
1. SIGINT signal received → initiates graceful shutdown
|
|
233
|
+
2. Stop signal sent to all workers via shutdown queues
|
|
234
|
+
3. Workers begin cleanup (close sockets, flush logs)
|
|
235
|
+
4. Parent waits for worker futures with timeout
|
|
236
|
+
5. InterpreterPoolExecutor shuts down
|
|
237
|
+
6. Subinterpreters are cleaned up
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
## Exceptions
|
|
241
|
+
|
|
242
|
+
The package provides custom exceptions for error handling:
|
|
243
|
+
|
|
244
|
+
- `WorkerException`: Base exception
|
|
245
|
+
- `WorkerInitializationError`: Worker failed to start
|
|
246
|
+
- `WorkerShutdownError`: Worker shutdown failed
|
|
247
|
+
- `ConfigurationError`: Invalid configuration
|
|
248
|
+
- `HookExecutionError`: Hook execution failed
|
|
249
|
+
- `TaskExecutionError`: Task execution failed
|
|
250
|
+
- `PoolExecutionError`: Pool-level error
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-melnottam"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Python package for managing Django application workers using InterpreterPoolExecutor"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "melhin", email = "melhin@duck.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"django",
|
|
17
|
+
"workers",
|
|
18
|
+
"interpreter-pool",
|
|
19
|
+
"concurrent",
|
|
20
|
+
"subinterpreters",
|
|
21
|
+
"hypercorn",
|
|
22
|
+
"asgi",
|
|
23
|
+
"wsgi",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Environment :: Web Environment",
|
|
28
|
+
"Framework :: Django",
|
|
29
|
+
"Framework :: Django :: 6.0",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.14",
|
|
35
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
dependencies = [
|
|
40
|
+
"Django>=6.0,<7.0",
|
|
41
|
+
"django-prodserver>=2.4.0",
|
|
42
|
+
"django-tasks-db>=0.12.0",
|
|
43
|
+
"hypercorn>=0.18.0",
|
|
44
|
+
"rich>=15.0.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
dev = [
|
|
49
|
+
"pytest>=7.0",
|
|
50
|
+
"pytest-django>=4.5",
|
|
51
|
+
"black>=23.0",
|
|
52
|
+
"ruff>=0.1.0",
|
|
53
|
+
"mypy>=1.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
django-melnottam-cli = "django_melnottam.cli:main"
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Homepage = "https://codeberg.org/melhin/django-melnottam"
|
|
61
|
+
Repository = "https://codeberg.org/melhin/django-melnottam"
|
|
62
|
+
Documentation = "https://codeberg.org/melhin/django-melnottam"
|
|
63
|
+
Issues = "https://codeberg.org/melhin/django-melnottam/issues"
|
|
64
|
+
|
|
65
|
+
[tool.setuptools]
|
|
66
|
+
package-dir = {"" = "src"}
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.packages.find]
|
|
69
|
+
where = ["src"]
|
|
70
|
+
|
|
71
|
+
[tool.black]
|
|
72
|
+
line-length = 100
|
|
73
|
+
target-version = ["py314"]
|
|
74
|
+
include = '\.pyi?$'
|
|
75
|
+
extend-exclude = '''
|
|
76
|
+
/(
|
|
77
|
+
# directories
|
|
78
|
+
\.eggs
|
|
79
|
+
| \.git
|
|
80
|
+
| \.hg
|
|
81
|
+
| \.mypy_cache
|
|
82
|
+
| \.tox
|
|
83
|
+
| \.venv
|
|
84
|
+
| build
|
|
85
|
+
| dist
|
|
86
|
+
| migrations
|
|
87
|
+
)/
|
|
88
|
+
'''
|
|
89
|
+
|
|
90
|
+
[tool.ruff]
|
|
91
|
+
line-length = 100
|
|
92
|
+
target-version = "py314"
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint]
|
|
95
|
+
select = [
|
|
96
|
+
"E", # pycodestyle errors
|
|
97
|
+
"W", # pycodestyle warnings
|
|
98
|
+
"F", # pyflakes
|
|
99
|
+
"I", # isort
|
|
100
|
+
"C", # flake8-comprehensions
|
|
101
|
+
"B", # flake8-bugbear
|
|
102
|
+
]
|
|
103
|
+
ignore = [
|
|
104
|
+
"E501", # line too long (handled by black)
|
|
105
|
+
"B008", # do not perform function calls in argument defaults
|
|
106
|
+
"B006", # do not use mutable data structures for argument defaults
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint.isort]
|
|
110
|
+
known-first-party = ["django-melnottam"]
|
|
111
|
+
|
|
112
|
+
[tool.ruff.format]
|
|
113
|
+
indent-style = "space"
|
|
114
|
+
line-ending = "auto"
|
|
115
|
+
|
|
116
|
+
[tool.mypy]
|
|
117
|
+
python_version = "3.14"
|
|
118
|
+
check_untyped_defs = true
|
|
119
|
+
ignore_missing_imports = true
|
|
120
|
+
warn_redundant_casts = true
|
|
121
|
+
warn_unused_configs = true
|
|
122
|
+
warn_unused_ignores = true
|
|
123
|
+
warn_return_any = true
|
|
124
|
+
|
|
125
|
+
[[tool.mypy.overrides]]
|
|
126
|
+
module = "django.*"
|
|
127
|
+
ignore_missing_imports = true
|
|
128
|
+
|
|
129
|
+
[[tool.mypy.overrides]]
|
|
130
|
+
module = "django_tasks_db.*"
|
|
131
|
+
ignore_missing_imports = true
|
|
132
|
+
|
|
133
|
+
[[tool.mypy.overrides]]
|
|
134
|
+
module = "hypercorn.*"
|
|
135
|
+
ignore_missing_imports = true
|
|
136
|
+
|
|
137
|
+
[dependency-groups]
|
|
138
|
+
dev = [
|
|
139
|
+
"pytest>=9.0.3",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
[[tool.uv.index]]
|
|
144
|
+
name = "testpypi"
|
|
145
|
+
url = "https://test.pypi.org/simple/"
|
|
146
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
147
|
+
explicit = true
|
|
File without changes
|