queuerPy 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,96 @@
1
+ """
2
+ Next interval function-related methods for the Python queuer implementation.
3
+ Mirrors Go's queuerNextInterval.go functionality.
4
+ """
5
+
6
+ import logging
7
+ from typing import Any, Callable
8
+
9
+ from queuer_global import QueuerGlobalMixin
10
+ from model.worker import Worker
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class QueuerNextIntervalMixin(QueuerGlobalMixin):
16
+ """
17
+ Mixin class containing next interval function-related methods for the Queuer.
18
+ Mirrors Go's queuerNextInterval.go functionality.
19
+ """
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ def add_next_interval_func(self, nif: Callable[..., Any]) -> Worker:
25
+ """
26
+ Add a NextIntervalFunc to the worker's available next interval functions.
27
+ Takes a NextIntervalFunc and adds it to the worker's available_next_interval_funcs.
28
+ The function name is derived from the function using helper.get_task_name_from_interface.
29
+
30
+ :param nif: The next interval function to add
31
+ :return: The updated worker after adding the function
32
+ :raises ValueError: If function is None
33
+ :raises RuntimeError: If function already exists or database update fails
34
+ """
35
+ from helper.task import get_task_name_from_interface
36
+
37
+ if nif is None:
38
+ raise ValueError("NextIntervalFunc cannot be None")
39
+
40
+ try:
41
+ nif_name = get_task_name_from_interface(nif)
42
+ except Exception as e:
43
+ raise RuntimeError(f"Error getting function name: {str(e)}")
44
+
45
+ if nif_name in self.worker.available_next_interval_funcs:
46
+ raise RuntimeError(f"NextIntervalFunc already exists: {nif_name}")
47
+
48
+ self.next_interval_funcs[nif_name] = nif
49
+ self.worker.available_next_interval_funcs.append(nif_name)
50
+
51
+ try:
52
+ worker = self.db_worker.update_worker(self.worker)
53
+ if not worker:
54
+ raise Exception("Worker could not be updated")
55
+
56
+ logger.info(f"NextInterval function added: {nif_name}")
57
+ return worker
58
+ except Exception as e:
59
+ raise RuntimeError(f"Error updating worker: {str(e)}")
60
+
61
+ def add_next_interval_func_with_name(
62
+ self, nif: Callable[..., Any], name: str
63
+ ) -> Worker:
64
+ """
65
+ Add a NextIntervalFunc to the worker's available next interval functions with a specific name.
66
+ Takes a NextIntervalFunc and a name, checks if the function is not None
67
+ and doesn't already exist in the worker's available next interval functions,
68
+ and adds it to the worker's available_next_interval_funcs.
69
+
70
+ This function is useful when you want to add a NextIntervalFunc
71
+ with a specific name that you control, rather than deriving it from the function itself.
72
+
73
+ :param nif: The next interval function to add
74
+ :param name: The specific name for the function
75
+ :return: The updated worker after adding the function with the specified name
76
+ :raises ValueError: If function is None
77
+ :raises RuntimeError: If function already exists or database update fails
78
+ """
79
+ if nif is None:
80
+ raise ValueError("NextIntervalFunc cannot be None")
81
+
82
+ if name in self.worker.available_next_interval_funcs:
83
+ raise RuntimeError(f"NextIntervalFunc with name already exists: {name}")
84
+
85
+ self.next_interval_funcs[name] = nif
86
+ self.worker.available_next_interval_funcs.append(name)
87
+
88
+ try:
89
+ worker = self.db_worker.update_worker(self.worker)
90
+ if not worker:
91
+ raise Exception("Worker could not be updated")
92
+
93
+ logger.info(f"NextInterval function added: {name}")
94
+ return worker
95
+ except Exception as e:
96
+ raise RuntimeError(f"Error updating worker: {str(e)}")
queuer_task.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ Task-related methods for the Python queuer implementation.
3
+ Mirrors Go's queuerTask.go functionality.
4
+ """
5
+
6
+ import logging
7
+ from typing import Any, Callable, Optional
8
+
9
+ from model.task import new_task, new_task_with_name
10
+ from model.task import Task
11
+ from queuer_global import QueuerGlobalMixin
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class QueuerTaskMixin(QueuerGlobalMixin):
17
+ """
18
+ Mixin class containing task-related methods for the Queuer.
19
+ Mirrors Go's queuerTask.go functionality.
20
+ """
21
+
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+ def add_task(self, task: Callable[..., Any]) -> Optional[Task]:
26
+ """
27
+ Add a new task to the queuer.
28
+ Creates a new task with the provided task function, adds it to the worker's available tasks,
29
+ and updates the worker in the database.
30
+ The task name is automatically generated based on the task's function name.
31
+
32
+ :param task: The task function to add
33
+ :return: The newly created task
34
+ :raises RuntimeError: If task creation fails or task already exists
35
+ """
36
+ task_name = task.__name__
37
+ if task_name in self.tasks:
38
+ raise RuntimeError(f"Task already exists: {task_name}")
39
+
40
+ try:
41
+ new_task_obj = new_task(task)
42
+ except Exception as e:
43
+ raise RuntimeError(f"Error creating new task: {str(e)}")
44
+
45
+ if new_task_obj.name in self.worker.available_tasks:
46
+ raise RuntimeError(f"Task already exists: {new_task_obj.name}")
47
+
48
+ self.tasks[new_task_obj.name] = new_task_obj
49
+ self.worker.available_tasks.append(new_task_obj.name)
50
+
51
+ # Update worker in DB
52
+ try:
53
+ self.db_worker.update_worker(self.worker)
54
+ except Exception as e:
55
+ raise RuntimeError(f"Error updating worker: {str(e)}")
56
+
57
+ logger.info(f"Task added: {new_task_obj.name}")
58
+ return new_task_obj
59
+
60
+ def add_task_with_name(self, task: Callable[..., Any], name: str) -> "Task":
61
+ """
62
+ Add a new task with a specific name to the queuer.
63
+ Creates a new task with the provided task function and name, adds it to the worker's available tasks,
64
+ and updates the worker in the database.
65
+
66
+ :param task: The task function to add
67
+ :param name: The specific name for the task
68
+ :return: The newly created task
69
+ :raises RuntimeError: If task creation fails or task already exists
70
+ """
71
+ try:
72
+ new_task_obj = new_task_with_name(task, name)
73
+ except Exception as e:
74
+ raise RuntimeError(f"Error creating new task: {str(e)}")
75
+
76
+ if name in self.worker.available_tasks:
77
+ raise RuntimeError(f"Task already exists: {name}")
78
+
79
+ self.tasks[new_task_obj.name] = new_task_obj
80
+ self.worker.available_tasks.append(new_task_obj.name)
81
+
82
+ # Update worker in DB
83
+ try:
84
+ self.db_worker.update_worker(self.worker)
85
+ except Exception as e:
86
+ raise RuntimeError(f"Error updating worker: {str(e)}")
87
+
88
+ logger.info(f"Task added: {new_task_obj.name}")
89
+ return new_task_obj
@@ -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
+ ```
@@ -0,0 +1,20 @@
1
+ _version.py,sha256=eSFgrYwpWV47W2rJ0KvoCggqPvGR_O9QWkjaZ_juYyA,185
2
+ queuer.py,sha256=N9h-C9tHyyD7zM2kLrvJ-jVt3Lo21Ko8YB9Arx_SqV0,22388
3
+ queuer_global.py,sha256=uu8mBdGGVC48YzevkRBfsEsV9BH-ZIaHCOmSKFoAiMo,1898
4
+ queuer_job.py,sha256=OSYyXnxQcCa5pIKuvvubiJvfHIWL_yqJ2yBTc5CMP8g,21923
5
+ queuer_listener.py,sha256=8cmyvlqtBw12WHs6p2AAMx_6jVr5nmXEqpSR8sLLj-k,2785
6
+ queuer_next_interval.py,sha256=zCFvUG7qsAWbL3WHxavHWhFIKUd4FiB9CD9mH_HNzzk,3786
7
+ queuer_task.py,sha256=KQ7ErKBYGZayJol-qilbapST5701rLUEkRnQZoApNps,3127
8
+ queuerPy/__init__.py,sha256=qf-UmlSq4oaSbB5Q5vfYCh5zOCozg2SCN67eLS_JkOU,1451
9
+ queuerPy/_version.py,sha256=eSFgrYwpWV47W2rJ0KvoCggqPvGR_O9QWkjaZ_juYyA,185
10
+ queuerPy/queuer.py,sha256=N9h-C9tHyyD7zM2kLrvJ-jVt3Lo21Ko8YB9Arx_SqV0,22388
11
+ queuerPy/queuer_global.py,sha256=uu8mBdGGVC48YzevkRBfsEsV9BH-ZIaHCOmSKFoAiMo,1898
12
+ queuerPy/queuer_job.py,sha256=OSYyXnxQcCa5pIKuvvubiJvfHIWL_yqJ2yBTc5CMP8g,21923
13
+ queuerPy/queuer_listener.py,sha256=8cmyvlqtBw12WHs6p2AAMx_6jVr5nmXEqpSR8sLLj-k,2785
14
+ queuerPy/queuer_next_interval.py,sha256=zCFvUG7qsAWbL3WHxavHWhFIKUd4FiB9CD9mH_HNzzk,3786
15
+ queuerPy/queuer_task.py,sha256=KQ7ErKBYGZayJol-qilbapST5701rLUEkRnQZoApNps,3127
16
+ queuerpy-0.1.0.dist-info/licenses/LICENSE,sha256=zjZ4NSeYpGd-UI9H4xtpI9sJaXhNCTPgl0ulk8wTgbY,554
17
+ queuerpy-0.1.0.dist-info/METADATA,sha256=Z1ea82fstshKIIbiHV7WZ7Q8jsMO3fKft-G4aEKPpFw,15381
18
+ queuerpy-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ queuerpy-0.1.0.dist-info/top_level.txt,sha256=lozuNzR1wzrC8Dpexls20DKCIC1AlBvb_FO8SJkmxtw,99
20
+ queuerpy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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,8 @@
1
+ _version
2
+ queuer
3
+ queuerPy
4
+ queuer_global
5
+ queuer_job
6
+ queuer_listener
7
+ queuer_next_interval
8
+ queuer_task