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