queuerPy 0.7.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.
Files changed (51) hide show
  1. queuerpy-0.7.0/LICENSE +13 -0
  2. queuerpy-0.7.0/MANIFEST.in +42 -0
  3. queuerpy-0.7.0/PKG-INFO +371 -0
  4. queuerpy-0.7.0/README.md +328 -0
  5. queuerpy-0.7.0/__init__.py +88 -0
  6. queuerpy-0.7.0/_version.py +7 -0
  7. queuerpy-0.7.0/core/__init__.py +19 -0
  8. queuerpy-0.7.0/core/broadcaster.py +77 -0
  9. queuerpy-0.7.0/core/listener.py +116 -0
  10. queuerpy-0.7.0/core/retryer.py +86 -0
  11. queuerpy-0.7.0/core/runner.py +274 -0
  12. queuerpy-0.7.0/core/scheduler.py +77 -0
  13. queuerpy-0.7.0/core/ticker.py +137 -0
  14. queuerpy-0.7.0/database/__init__.py +48 -0
  15. queuerpy-0.7.0/database/db_job.py +460 -0
  16. queuerpy-0.7.0/database/db_listener.py +159 -0
  17. queuerpy-0.7.0/database/db_master.py +134 -0
  18. queuerpy-0.7.0/database/db_worker.py +253 -0
  19. queuerpy-0.7.0/helper/__init__.py +69 -0
  20. queuerpy-0.7.0/helper/database.py +489 -0
  21. queuerpy-0.7.0/helper/error.py +39 -0
  22. queuerpy-0.7.0/helper/logging.py +259 -0
  23. queuerpy-0.7.0/helper/sql.py +225 -0
  24. queuerpy-0.7.0/helper/task.py +188 -0
  25. queuerpy-0.7.0/model/__init__.py +39 -0
  26. queuerpy-0.7.0/model/batch_job.py +20 -0
  27. queuerpy-0.7.0/model/connection.py +30 -0
  28. queuerpy-0.7.0/model/job.py +244 -0
  29. queuerpy-0.7.0/model/master.py +106 -0
  30. queuerpy-0.7.0/model/options.py +130 -0
  31. queuerpy-0.7.0/model/options_on_error.py +112 -0
  32. queuerpy-0.7.0/model/task.py +80 -0
  33. queuerpy-0.7.0/model/worker.py +147 -0
  34. queuerpy-0.7.0/pyproject.toml +89 -0
  35. queuerpy-0.7.0/queuer.py +586 -0
  36. queuerpy-0.7.0/queuerPy.egg-info/PKG-INFO +371 -0
  37. queuerpy-0.7.0/queuerPy.egg-info/SOURCES.txt +62 -0
  38. queuerpy-0.7.0/queuerPy.egg-info/dependency_links.txt +1 -0
  39. queuerpy-0.7.0/queuerPy.egg-info/requires.txt +16 -0
  40. queuerpy-0.7.0/queuerPy.egg-info/top_level.txt +1 -0
  41. queuerpy-0.7.0/queuer_global.py +65 -0
  42. queuerpy-0.7.0/queuer_job.py +609 -0
  43. queuerpy-0.7.0/queuer_listener.py +73 -0
  44. queuerpy-0.7.0/queuer_master.py +163 -0
  45. queuerpy-0.7.0/queuer_next_interval.py +96 -0
  46. queuerpy-0.7.0/queuer_task.py +116 -0
  47. queuerpy-0.7.0/setup.cfg +4 -0
  48. queuerpy-0.7.0/sql/job.sql +814 -0
  49. queuerpy-0.7.0/sql/master.sql +99 -0
  50. queuerpy-0.7.0/sql/notify.sql +30 -0
  51. queuerpy-0.7.0/sql/worker.sql +285 -0
queuerpy-0.7.0/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2025 Simon Herrmann
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,42 @@
1
+ # Include important documentation and configuration files
2
+ include README.md
3
+ include LICENSE
4
+ include pyproject.toml
5
+ include _version.py
6
+
7
+ # Include all Python modules and packages
8
+ recursive-include core *.py
9
+ recursive-include database *.py
10
+ recursive-include helper *.py
11
+ recursive-include model *.py
12
+
13
+ # Include SQL files that are needed for database setup
14
+ recursive-include sql *.sql
15
+
16
+ # Include type stubs if any
17
+ recursive-include typings *.pyi
18
+
19
+ # Exclude unnecessary files and directories
20
+ global-exclude __pycache__
21
+ global-exclude *.py[co]
22
+ global-exclude .coverage
23
+ global-exclude coverage.xml
24
+ global-exclude coverage-badge.svg
25
+
26
+ # Exclude test files
27
+ recursive-exclude * *_test.py
28
+ recursive-exclude * test_*.py
29
+ exclude pytest.ini
30
+
31
+ # Exclude development and build artifacts
32
+ exclude .git*
33
+ exclude .vscode
34
+ exclude .pytest_cache
35
+ exclude .venv
36
+ prune __pycache__
37
+ prune .pytest_cache
38
+ prune .venv
39
+ prune .git
40
+
41
+ # Exclude example directory if you don't want it in the package
42
+ # recursive-exclude example *
@@ -0,0 +1,371 @@
1
+ Metadata-Version: 2.4
2
+ Name: queuerPy
3
+ Version: 0.7.0
4
+ Summary: A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend
5
+ Author-email: Simon Herrmann <siherrmann@users.noreply.github.com>
6
+ Maintainer-email: Simon Herrmann <siherrmann@users.noreply.github.com>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/siherrmann/queuerPy
9
+ Project-URL: Repository, https://github.com/siherrmann/queuerPy
10
+ Project-URL: Issues, https://github.com/siherrmann/queuerPy/issues
11
+ Project-URL: Documentation, https://github.com/siherrmann/queuerPy#readme
12
+ Keywords: queue,job,task,postgresql,worker,async
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: System :: Distributed Computing
24
+ Classifier: Topic :: Database
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: psycopg[binary]>=3.1.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
34
+ Requires-Dist: testcontainers[postgres]>=3.7.0; extra == "dev"
35
+ Requires-Dist: psutil>=5.8.0; extra == "dev"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest>=7.0.0; extra == "test"
38
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
39
+ Requires-Dist: pytest-mock>=3.14.0; extra == "test"
40
+ Requires-Dist: testcontainers[postgres]>=3.7.0; extra == "test"
41
+ Requires-Dist: psutil>=5.8.0; extra == "test"
42
+ Dynamic: license-file
43
+
44
+ # queuerPy
45
+
46
+ [![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://www.python.org/)
47
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/siherrmann/queuer/blob/master/LICENSE)
48
+ ![Coverage](https://raw.githubusercontent.com/siherrmann/queuerPy/refs/heads/main/coverage-badge.svg)
49
+
50
+ Python port of the queuer package - a queueing system based on PostgreSQL.
51
+
52
+ ## 💡 Goal of this package
53
+
54
+ This queuer is meant to be as easy as possible to use. No specific function signature (except for returning results or raising exceptions for error handling), easy setup and still fast.
55
+
56
+ The job table contains only queued, scheduled and running tasks. The ended jobs (succeeded, cancelled, failed) are moved to a job_archive table.
57
+
58
+ ---
59
+
60
+ # 🛠️ Installation
61
+
62
+ ## PyPI Installation (Recommended)
63
+
64
+ ```bash
65
+ pip install queuerPy
66
+ ```
67
+
68
+ To use the package you also need a running postgres database with the timescaleDB extension. You can use the `docker-compose.yml` file in the example folder or start a Docker container with the `timescale/timescaledb:latest-pg17` image.
69
+
70
+ ---
71
+
72
+ ## 🚀 Getting started
73
+
74
+ The full initialisation is (in the easiest case):
75
+
76
+ ```python
77
+ from queuer import new_queuer
78
+
79
+ # Create a new queuer instance
80
+ q = new_queuer("exampleWorker", 3)
81
+
82
+ # Add a task to the queuer
83
+ q.add_task(example_task)
84
+
85
+ # Start the queuer
86
+ q.start()
87
+ ```
88
+
89
+ You can also add a task with a decorator so the tasks don't have to be added to some central function:
90
+
91
+ ```python
92
+ @queuer.task(name="optionalName")
93
+ def example_task():
94
+ ...
95
+ ```
96
+
97
+ That's easy, right? Adding a job is just as easy:
98
+
99
+ ```python
100
+ # Add a job to the queue
101
+ job = q.add_job(example_task, 5, "12")
102
+ print(f"Job added: {job.rid}")
103
+ ```
104
+
105
+ In the initialisation of the queuer the existence of the necessary database tables is checked and if they don't exist they get created. The database is configured with these environment variables:
106
+
107
+ ```bash
108
+ export QUEUER_DB_HOST=localhost
109
+ export QUEUER_DB_PORT=5432
110
+ export QUEUER_DB_DATABASE=postgres
111
+ export QUEUER_DB_USERNAME=username
112
+ export QUEUER_DB_PASSWORD=password1234
113
+ export QUEUER_DB_SCHEMA=public
114
+ ```
115
+
116
+ You can find a full example in the example folder.
117
+
118
+ ---
119
+
120
+ ## new_queuer
121
+
122
+ `new_queuer` is a convenience constructor that creates a new Queuer instance using default database configuration derived from environment variables. It acts as a wrapper around `new_queuer_with_db`. The encryption key for the database is taken from the `QUEUER_ENCRYPTION_KEY` environment variable; if not provided, it defaults to unencrypted results.
123
+
124
+ `new_queuer_with_db` is the primary constructor for creating a new Queuer instance. It allows for explicit database configuration and encryption key specification, and initializes all necessary components, including database handlers, internal event listeners, and the worker.
125
+
126
+ ```python
127
+ def new_queuer(name: str, max_concurrency: int, *options: OnError) -> Queuer
128
+
129
+ def new_queuer_with_db(
130
+ name: str,
131
+ max_concurrency: int,
132
+ encryption_key: str,
133
+ db_config: DatabaseConfiguration,
134
+ *options: OnError
135
+ ) -> Queuer
136
+ ```
137
+
138
+ - `name`: A `str` identifier for this queuer instance.
139
+ - `max_concurrency`: An `int` specifying the maximum number of jobs this queuer can process concurrently.
140
+ - `encryption_key`: A `str` used for encrypting sensitive job data in the database. If empty, results will be stored unencrypted.
141
+ - `db_config`: An optional `DatabaseConfiguration`. If None, the configuration will be loaded from environment variables.
142
+ - `options`: Optional `OnError` configurations to apply to the worker.
143
+
144
+ This function performs the following setup:
145
+ - Initializes a logger.
146
+ - Sets up the database connection using the provided `db_config` or environment variables.
147
+ - Creates `JobDBHandler`, `WorkerDBHandler` instances for database interactions.
148
+ - Initializes internal notification listeners for `job_insert`, `job_update`, and `job_delete` events.
149
+ - Creates and inserts a new `Worker` into the database based on the provided `name`, `max_concurrency`, and `options`.
150
+ - If any critical error occurs during this initialization (e.g., database connection failure, worker creation error), the function will raise an exception.
151
+
152
+ ---
153
+
154
+ ## start
155
+
156
+ The `start` method initiates the operational lifecycle of the Queuer. It sets up the main processing loops, initializes database listeners, and begins the job processing and polling loops.
157
+
158
+ ```python
159
+ def start(self) -> None
160
+ ```
161
+
162
+ Upon calling `start`:
163
+ - It performs a basic check to ensure internal listeners are initialized.
164
+ - Database listeners are created to listen to job events (inserts, updates, deletes) via PostgreSQL NOTIFY/LISTEN.
165
+ - It starts a poller to periodically poll the database for new jobs to process.
166
+ - It starts a heartbeat ticker to keep the worker status updated.
167
+ - The method returns immediately after starting all background processes.
168
+
169
+ The method includes proper error handling and will raise exceptions if the queuer is not properly initialized or if there's an error creating the database listeners.
170
+
171
+ ---
172
+
173
+ ## stop
174
+
175
+ The `stop` method gracefully shuts down the Queuer instance, releasing resources and ensuring ongoing operations are properly concluded.
176
+
177
+ ```python
178
+ def stop(self) -> None
179
+ ```
180
+
181
+ The `stop` method cancels all jobs, closes database listeners, and cleans up resources.
182
+
183
+ ---
184
+
185
+ ## add_task
186
+
187
+ The `add_task` method registers a new job task with the queuer. A task is the actual function that will be executed when a job associated with it is processed.
188
+
189
+ ```python
190
+ def add_task(self, task: Callable) -> Task
191
+
192
+ def add_task_with_name(self, task: Callable, name: str) -> Task
193
+ ```
194
+
195
+ - `task`: A `Callable` representing the function that will serve as the job's executable logic. The queuer will automatically derive a name for this task based on its function name (e.g., `my_task_function`). The derived name must be unique if no `name` is given.
196
+ - `name`: A `str` specifying the custom name for this task. This name must be unique within the queuer's tasks.
197
+
198
+ This method handles the registration of a task, making the worker able to pick up and execute a job of this task type. It also updates the worker's available tasks in the database. The task should be added before starting the queuer. If there's an issue during task creation or database update, an exception will be raised.
199
+
200
+ ---
201
+
202
+ ## add_next_interval_func
203
+
204
+ The `add_next_interval_func` method registers a custom function that determines the next execution time for scheduled jobs. This is useful for implementing complex scheduling logic beyond simple fixed intervals.
205
+
206
+ ```python
207
+ def add_next_interval_func(self, nif: Callable) -> Worker
208
+
209
+ def add_next_interval_func_with_name(self, nif: Callable, name: str) -> Worker
210
+ ```
211
+
212
+ - `nif`: A `Callable` defining custom logic for calculating the next interval. The queuer will automatically derive a name for this function. The derived name must be unique if no `name` is given.
213
+ - `name`: A `str` specifying the custom name for this NextIntervalFunc. This name must be unique within the queuer's NextIntervalFuncs.
214
+
215
+ This method adds the provided NextIntervalFunc to the queuer's available functions, making it usable for jobs with custom scheduling requirements. It updates the worker's configuration in the database.
216
+
217
+ ---
218
+
219
+ ## Worker Options
220
+
221
+ The OnError class defines how a worker should handle errors when processing a job. This allows for configurable retry behavior.
222
+
223
+ ```python
224
+ class OnError:
225
+ def __init__(
226
+ self,
227
+ timeout: float = 30.0,
228
+ max_retries: int = 3,
229
+ retry_delay: float = 1.0,
230
+ retry_backoff: str = RetryBackoff.NONE
231
+ )
232
+ ```
233
+
234
+ - `timeout`: The maximum time (in seconds) allowed for a single attempt of a job. If the job exceeds this duration, it's considered to have timed out.
235
+ - `max_retries`: The maximum number of times a job will be retried after a failure.
236
+ - `retry_delay`: The initial delay (in seconds) before the first retry attempt. This delay can be modified by the `retry_backoff` strategy.
237
+ - `retry_backoff`: Specifies the strategy used to increase the delay between subsequent retries.
238
+
239
+ #### Retry Backoff Strategies
240
+
241
+ The RetryBackoff enum defines the available strategies for increasing retry delays:
242
+
243
+ ```python
244
+ class RetryBackoff(str, Enum):
245
+ NONE = "none"
246
+ LINEAR = "linear"
247
+ EXPONENTIAL = "exponential"
248
+ ```
249
+
250
+ - `RETRY_BACKOFF_NONE`: No backoff. The retry_delay remains constant for all retries.
251
+ - `RETRY_BACKOFF_LINEAR`: The retry delay increases linearly with each attempt (e.g., delay, 2*delay, 3*delay).
252
+ - `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2*2).
253
+
254
+ ---
255
+
256
+ ## Job options
257
+
258
+ The Options class allows you to define specific behaviors for individual jobs, overriding default worker settings where applicable.
259
+
260
+ ```python
261
+ @dataclass
262
+ class Options:
263
+ on_error: Optional[OnError] = None
264
+ schedule: Optional[Schedule] = None
265
+ ```
266
+
267
+ - `on_error`: An optional `OnError` configuration that will override the worker's default error handling for this specific job. This allows you to define unique retry logic per job.
268
+ - `schedule`: An optional `Schedule` configuration for jobs that need to be executed at recurring intervals.
269
+
270
+ ### OnError for jobs
271
+
272
+ The OnError class for jobs is identical to the one used for worker options, allowing granular control over error handling for individual jobs.
273
+
274
+ ### Schedule
275
+
276
+ The Schedule class is used to define recurring jobs.
277
+
278
+ ```python
279
+ @dataclass
280
+ class Schedule:
281
+ start: datetime = None
282
+ max_count: int = 1
283
+ interval: Optional[timedelta] = None
284
+ next_interval: Optional[str] = None
285
+ ```
286
+
287
+ - `start`: The initial time at which the scheduled job should first run.
288
+ - `max_count`: The maximum number of times the job should be executed. A value of 0 indicates an indefinite number of repetitions (run forever).
289
+ - `interval`: The duration between consecutive executions of the scheduled job.
290
+ - `next_interval`: Function name of the NextIntervalFunc returning the time of the next execution of the scheduled job. **Either `interval` or `next_interval` have to be set if the `max_count` is 0 or greater than 1.**
291
+
292
+ ---
293
+
294
+ ## Additional Methods
295
+
296
+ ### Job Management
297
+
298
+ ```python
299
+ # Add a single job
300
+ job = queuer.add_job(my_task, param1, param2)
301
+
302
+ # Add a job with custom options
303
+ from model.options import Options, OnError
304
+ options = Options(on_error=OnError(max_retries=5, timeout=60.0))
305
+ job = queuer.add_job_with_options(options, my_task, param1)
306
+
307
+ # Add multiple jobs as a batch
308
+ from model.batch_job import BatchJob
309
+ batch = [
310
+ BatchJob(task=my_task, parameters=[1, "a"]),
311
+ BatchJob(task=my_task, parameters=[2, "b"])
312
+ ]
313
+ queuer.add_jobs(batch)
314
+
315
+ # Wait for a job to finish
316
+ finished_job = queuer.wait_for_job_finished(job.rid, timeout_seconds=30.0)
317
+
318
+ # Get job information
319
+ job_info = queuer.get_job(job.rid)
320
+ archived_job = queuer.get_job_ended(job.rid) # For completed jobs
321
+ ```
322
+
323
+ ### Job Queries
324
+
325
+ ```python
326
+ # Get jobs by status
327
+ running_jobs = queuer.get_jobs(status="RUNNING")
328
+ all_jobs = queuer.get_jobs()
329
+
330
+ # Get jobs by worker
331
+ worker_jobs = queuer.get_jobs_by_worker_rid(worker.rid)
332
+ ```
333
+
334
+ ---
335
+
336
+ # ⭐ Features
337
+
338
+ - **Async/Await Support**: Full asyncio integration with threading fallbacks.
339
+ - **PostgreSQL NOTIFY/LISTEN**: Real-time job notifications without polling overhead.
340
+ - **Batch Job Processing**: Insert job batches efficiently using PostgreSQL's `COPY FROM` feature.
341
+ - **Panic Recovery**: Automatic recovery for all running jobs in case of unexpected failures.
342
+ - **Error Handling**: Comprehensive error handling by checking last output parameter for errors.
343
+ - **Multiple Workers**: Multiple queuer instances can run across different microservices while maintaining job start order and isolation.
344
+ - **Scheduled Jobs**: Support for scheduled and periodic jobs with custom intervals.
345
+ - **Job Lifecycle Management**: Easy functions to get jobs and workers, track job status.
346
+ - **Event Listeners**: Listen for job updates, completion, and deletion events (ended jobs).
347
+ - **Job Completion Helpers**: Helper functions to listen for specific finished jobs.
348
+ - **Retry Mechanisms**: Retry mechanism for ended jobs which creates a new job with the same parameters, with configurable retry logic and different backoff strategies.
349
+ - **Custom Scheduling**: Custom NextInterval functions to address custom needs for scheduling (e.g., scheduling with timezone offset).
350
+ - **Master Worker Management**: Automatic master worker setting retention and other central settings. Automatic switch to new master if old worker stops.
351
+ - **Heartbeat System**: Worker heartbeat monitoring and automatic stale worker detection and cancellation by the master.
352
+ - **Encryption Support**: Encryption support for sensitive job data stored in the database.
353
+ - **Database Integration**: Seamless PostgreSQL integration with automatic schema management.
354
+ - **Type Safety**: Full type hints and dataclass-based models for better development experience.
355
+
356
+ > **Note**: Transactional job insert is the only feature that is not yet implemented in the Python implementation of the queuer.
357
+
358
+ ---
359
+
360
+ # 🧪 Testing
361
+
362
+ ```bash
363
+ # Run all tests
364
+ python -m pytest
365
+
366
+ # Run with coverage
367
+ python -m pytest --cov=. --cov-report=html
368
+
369
+ # Run specific test files
370
+ python -m pytest queuer_test.py -v
371
+ ```