queuerPy 0.19.0__tar.gz → 0.20.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.19.0/queuerPy.egg-info → queuerpy-0.20.0}/PKG-INFO +1 -1
- {queuerpy-0.19.0 → queuerpy-0.20.0}/_version.py +1 -1
- {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_job.py +25 -12
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/database.py +5 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/pyproject.toml +2 -2
- {queuerpy-0.19.0 → queuerpy-0.20.0/queuerPy.egg-info}/PKG-INFO +1 -1
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_global.py +1 -1
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_master.py +25 -14
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_worker.py +13 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/job.sql +58 -7
- {queuerpy-0.19.0 → queuerpy-0.20.0}/LICENSE +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/MANIFEST.in +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/README.md +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/__init__.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/__init__.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/broadcaster.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/listener.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/retryer.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/runner.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/scheduler.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/core/ticker.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/database/__init__.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_listener.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_master.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_worker.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/__init__.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/error.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/logging.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/sql.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/task.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/__init__.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/batch_job.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/connection.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/job.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/master.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/options.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/options_on_error.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/task.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/model/worker.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/py.typed +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/SOURCES.txt +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/dependency_links.txt +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/requires.txt +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/top_level.txt +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_job.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_listener.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_next_interval.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_task.py +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/setup.cfg +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/master.sql +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/notify.sql +0 -0
- {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/worker.sql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queuerPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend
|
|
5
5
|
Author-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
@@ -9,7 +9,7 @@ from typing import List, Optional, Tuple
|
|
|
9
9
|
from uuid import UUID
|
|
10
10
|
from psycopg.rows import dict_row
|
|
11
11
|
|
|
12
|
-
from ..helper.database import Database
|
|
12
|
+
from ..helper.database import Database, DatabaseConfiguration
|
|
13
13
|
from ..helper.error import QueuerError
|
|
14
14
|
from ..helper.sql import SQLLoader, run_ddl
|
|
15
15
|
from ..model.job import Job
|
|
@@ -25,7 +25,7 @@ class JobDBHandler:
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
27
|
db_connection: Database,
|
|
28
|
-
|
|
28
|
+
db_config: DatabaseConfiguration,
|
|
29
29
|
encryption_key: str = "",
|
|
30
30
|
):
|
|
31
31
|
"""Initialize job database handler."""
|
|
@@ -35,12 +35,13 @@ class JobDBHandler:
|
|
|
35
35
|
raise ValueError("Database connection is not established")
|
|
36
36
|
|
|
37
37
|
self.encryption_key: str = encryption_key
|
|
38
|
+
self.with_timescale: bool = db_config.with_timescale
|
|
38
39
|
|
|
39
40
|
sql_loader: SQLLoader = SQLLoader()
|
|
40
|
-
sql_loader.load_notify_sql(self.db.instance, force=with_table_drop)
|
|
41
|
-
sql_loader.load_job_sql(self.db.instance, force=with_table_drop)
|
|
41
|
+
sql_loader.load_notify_sql(self.db.instance, force=db_config.with_table_drop)
|
|
42
|
+
sql_loader.load_job_sql(self.db.instance, force=db_config.with_table_drop)
|
|
42
43
|
|
|
43
|
-
if with_table_drop:
|
|
44
|
+
if db_config.with_table_drop:
|
|
44
45
|
self.drop_tables()
|
|
45
46
|
|
|
46
47
|
self.create_table()
|
|
@@ -72,7 +73,7 @@ class JobDBHandler:
|
|
|
72
73
|
if self.db.instance is None:
|
|
73
74
|
raise ValueError("Database connection is not established")
|
|
74
75
|
|
|
75
|
-
run_ddl(self.db.instance, "SELECT init_job();")
|
|
76
|
+
run_ddl(self.db.instance, f"SELECT init_job({self.with_timescale});")
|
|
76
77
|
|
|
77
78
|
def drop_tables(self) -> None:
|
|
78
79
|
"""Drop job tables with DDL deadlock protection."""
|
|
@@ -379,9 +380,15 @@ class JobDBHandler:
|
|
|
379
380
|
if self.db.instance is None:
|
|
380
381
|
raise ValueError("Database connection is not established")
|
|
381
382
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
383
|
+
try:
|
|
384
|
+
with self.db.instance.cursor() as cur:
|
|
385
|
+
cur.execute(
|
|
386
|
+
"SELECT add_retention_archive(%s, %s);", (days, self.with_timescale)
|
|
387
|
+
)
|
|
388
|
+
self.db.instance.commit()
|
|
389
|
+
except Exception as e:
|
|
390
|
+
self.db.instance.rollback()
|
|
391
|
+
raise e
|
|
385
392
|
|
|
386
393
|
def remove_retention_archive(self) -> None:
|
|
387
394
|
"""
|
|
@@ -391,9 +398,15 @@ class JobDBHandler:
|
|
|
391
398
|
if self.db.instance is None:
|
|
392
399
|
raise ValueError("Database connection is not established")
|
|
393
400
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
401
|
+
try:
|
|
402
|
+
with self.db.instance.cursor() as cur:
|
|
403
|
+
cur.execute(
|
|
404
|
+
"SELECT remove_retention_archive(%s);", (self.with_timescale,)
|
|
405
|
+
)
|
|
406
|
+
self.db.instance.commit()
|
|
407
|
+
except Exception as e:
|
|
408
|
+
self.db.instance.rollback()
|
|
409
|
+
raise e
|
|
397
410
|
|
|
398
411
|
def select_job_from_archive(self, rid: UUID) -> Optional[Job]:
|
|
399
412
|
"""
|
|
@@ -30,6 +30,7 @@ class DatabaseConfiguration:
|
|
|
30
30
|
schema: str = "public"
|
|
31
31
|
sslmode: str = "require"
|
|
32
32
|
with_table_drop: bool = False
|
|
33
|
+
with_timescale: bool = True
|
|
33
34
|
|
|
34
35
|
@classmethod
|
|
35
36
|
def from_env(cls) -> "DatabaseConfiguration":
|
|
@@ -45,6 +46,9 @@ class DatabaseConfiguration:
|
|
|
45
46
|
with_table_drop = (
|
|
46
47
|
os.getenv("QUEUER_DB_WITH_TABLE_DROP", "false").lower() == "true"
|
|
47
48
|
)
|
|
49
|
+
with_timescale = (
|
|
50
|
+
os.getenv("QUEUER_DB_WITH_TIMESCALE", "true").lower() != "false"
|
|
51
|
+
)
|
|
48
52
|
|
|
49
53
|
# Validate required fields
|
|
50
54
|
if not all(
|
|
@@ -72,6 +76,7 @@ class DatabaseConfiguration:
|
|
|
72
76
|
schema=schema,
|
|
73
77
|
sslmode=sslmode,
|
|
74
78
|
with_table_drop=with_table_drop,
|
|
79
|
+
with_timescale=with_timescale,
|
|
75
80
|
)
|
|
76
81
|
|
|
77
82
|
def connection_string(self) -> str:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "queuerPy"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.20.0"
|
|
8
8
|
description = "A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -92,7 +92,7 @@ pythonVersion = "3.8"
|
|
|
92
92
|
pythonPlatform = "All"
|
|
93
93
|
|
|
94
94
|
[tool.mypy]
|
|
95
|
-
python_version = "0.
|
|
95
|
+
python_version = "0.20.0"
|
|
96
96
|
warn_return_any = true
|
|
97
97
|
warn_unused_configs = true
|
|
98
98
|
disallow_untyped_defs = true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queuerPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend
|
|
5
5
|
Author-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
@@ -56,7 +56,7 @@ class QueuerGlobalMixin:
|
|
|
56
56
|
|
|
57
57
|
# Database handlers
|
|
58
58
|
self.db_job: JobDBHandler = JobDBHandler(
|
|
59
|
-
self.database, db_config
|
|
59
|
+
self.database, db_config, encryption_key
|
|
60
60
|
)
|
|
61
61
|
self.db_worker: WorkerDBHandler = WorkerDBHandler(
|
|
62
62
|
self.database, db_config.with_table_drop
|
|
@@ -38,14 +38,27 @@ class QueuerMasterMixin(QueuerGlobalMixin):
|
|
|
38
38
|
|
|
39
39
|
# Handle retention archive configuration
|
|
40
40
|
try:
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
if master_settings.retention_archive > 0:
|
|
42
|
+
if old_master.settings.retention_archive == 0:
|
|
43
|
+
self.db_job.add_retention_archive(master_settings.retention_archive)
|
|
44
|
+
elif (
|
|
45
|
+
old_master.settings.retention_archive
|
|
46
|
+
!= master_settings.retention_archive
|
|
47
|
+
):
|
|
48
|
+
try:
|
|
49
|
+
self.db_job.remove_retention_archive()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.warning(
|
|
52
|
+
f"Could not remove old retention policy (may not exist): {e}"
|
|
53
|
+
)
|
|
54
|
+
self.db_job.add_retention_archive(master_settings.retention_archive)
|
|
55
|
+
elif old_master.settings.retention_archive > 0:
|
|
56
|
+
try:
|
|
57
|
+
self.db_job.remove_retention_archive()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning(
|
|
60
|
+
f"Could not remove retention policy (may not exist): {e}"
|
|
61
|
+
)
|
|
49
62
|
except Exception as e:
|
|
50
63
|
raise QueuerError("managing retention archive", e)
|
|
51
64
|
|
|
@@ -83,10 +96,9 @@ class QueuerMasterMixin(QueuerGlobalMixin):
|
|
|
83
96
|
logger.error(f"Error in master ticker task: {e}")
|
|
84
97
|
|
|
85
98
|
ticker = Ticker(
|
|
86
|
-
task=ticker_task,
|
|
87
|
-
name="_master_ticker",
|
|
88
99
|
interval=timedelta(seconds=master_settings.master_poll_interval),
|
|
89
|
-
|
|
100
|
+
task=ticker_task,
|
|
101
|
+
use_mp=False, # Use threading instead of multiprocessing to avoid pickling issues
|
|
90
102
|
)
|
|
91
103
|
logger.info("Starting master ticker...")
|
|
92
104
|
ticker.go()
|
|
@@ -150,10 +162,9 @@ class QueuerMasterMixin(QueuerGlobalMixin):
|
|
|
150
162
|
|
|
151
163
|
# Create and start the polling ticker
|
|
152
164
|
ticker = Ticker(
|
|
153
|
-
task=ticker_task,
|
|
154
|
-
name="_poll_master_ticker",
|
|
155
165
|
interval=timedelta(seconds=master_settings.master_poll_interval),
|
|
156
|
-
|
|
166
|
+
task=ticker_task,
|
|
167
|
+
use_mp=False, # Use threading instead of multiprocessing to avoid pickling issues
|
|
157
168
|
)
|
|
158
169
|
|
|
159
170
|
logger.info("Starting master poll ticker...")
|
|
@@ -15,6 +15,19 @@ from .queuer_global import QueuerGlobalMixin
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def get_current_worker_rid(self) -> Optional[UUID]:
|
|
19
|
+
"""
|
|
20
|
+
Get the current worker's RID safely with mutex locking.
|
|
21
|
+
Mirrors Go's GetCurrentWorkerRID method.
|
|
22
|
+
|
|
23
|
+
:return: Worker RID or None if no worker
|
|
24
|
+
"""
|
|
25
|
+
with self.worker_mutex:
|
|
26
|
+
if self.worker is not None:
|
|
27
|
+
return self.worker.rid
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
18
31
|
class QueuerWorkerMixin(QueuerGlobalMixin):
|
|
19
32
|
"""
|
|
20
33
|
Mixin class containing worker-related methods for the Queuer.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
CREATE OR REPLACE FUNCTION init_job() RETURNS VOID AS $$
|
|
1
|
+
CREATE OR REPLACE FUNCTION init_job(enable_timescaledb BOOLEAN DEFAULT TRUE) RETURNS VOID AS $$
|
|
2
2
|
BEGIN
|
|
3
3
|
-- Create TimescaleDB extension
|
|
4
|
-
|
|
4
|
+
IF enable_timescaledb THEN
|
|
5
|
+
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
|
|
6
|
+
END IF;
|
|
5
7
|
|
|
6
8
|
-- Create pgcrypto extension
|
|
7
9
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
@@ -37,7 +39,9 @@ BEGIN
|
|
|
37
39
|
);
|
|
38
40
|
|
|
39
41
|
-- Create hypertable (TimescaleDB feature)
|
|
40
|
-
|
|
42
|
+
IF enable_timescaledb THEN
|
|
43
|
+
PERFORM create_hypertable('job_archive', by_range('updated_at'), if_not_exists => TRUE);
|
|
44
|
+
END IF;
|
|
41
45
|
|
|
42
46
|
-- Create triggers for notifications
|
|
43
47
|
CREATE OR REPLACE TRIGGER job_notify_event
|
|
@@ -698,17 +702,64 @@ BEGIN
|
|
|
698
702
|
END;
|
|
699
703
|
$$ LANGUAGE plpgsql;
|
|
700
704
|
|
|
701
|
-
CREATE OR REPLACE FUNCTION add_retention_archive(input_retention_days INT)
|
|
705
|
+
CREATE OR REPLACE FUNCTION add_retention_archive(input_retention_days INT, enable_timescaledb BOOLEAN DEFAULT TRUE)
|
|
702
706
|
RETURNS VOID AS $$
|
|
703
707
|
BEGIN
|
|
704
|
-
|
|
708
|
+
IF enable_timescaledb THEN
|
|
709
|
+
-- Use TimescaleDB retention policy
|
|
710
|
+
PERFORM add_retention_policy('job_archive', (input_retention_days * INTERVAL '1 day'));
|
|
711
|
+
ELSE
|
|
712
|
+
-- Create sequence for tracking inserts
|
|
713
|
+
CREATE SEQUENCE IF NOT EXISTS job_archive_insert_counter;
|
|
714
|
+
|
|
715
|
+
-- Create trigger function that deletes old records every 10th insert
|
|
716
|
+
EXECUTE format('
|
|
717
|
+
CREATE OR REPLACE FUNCTION cleanup_job_archive_trigger()
|
|
718
|
+
RETURNS TRIGGER AS $func$
|
|
719
|
+
DECLARE
|
|
720
|
+
insert_count BIGINT;
|
|
721
|
+
deleted_count INT;
|
|
722
|
+
BEGIN
|
|
723
|
+
-- Only run cleanup every 10th insert
|
|
724
|
+
insert_count := nextval(''job_archive_insert_counter'');
|
|
725
|
+
IF insert_count %% 10 = 0 THEN
|
|
726
|
+
DELETE FROM job_archive
|
|
727
|
+
WHERE updated_at < (CURRENT_TIMESTAMP - INTERVAL ''%s days'');
|
|
728
|
+
|
|
729
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
|
730
|
+
|
|
731
|
+
-- Log cleanup if any rows were deleted (optional)
|
|
732
|
+
IF deleted_count > 0 THEN
|
|
733
|
+
RAISE NOTICE ''Cleaned up %% old job_archive records'', deleted_count;
|
|
734
|
+
END IF;
|
|
735
|
+
END IF;
|
|
736
|
+
RETURN NEW;
|
|
737
|
+
END;
|
|
738
|
+
$func$ LANGUAGE plpgsql;
|
|
739
|
+
', input_retention_days);
|
|
740
|
+
|
|
741
|
+
-- Create trigger on job_archive
|
|
742
|
+
DROP TRIGGER IF EXISTS job_archive_cleanup_trigger ON job_archive;
|
|
743
|
+
CREATE TRIGGER job_archive_cleanup_trigger
|
|
744
|
+
AFTER INSERT ON job_archive
|
|
745
|
+
FOR EACH ROW
|
|
746
|
+
EXECUTE FUNCTION cleanup_job_archive_trigger();
|
|
747
|
+
END IF;
|
|
705
748
|
END;
|
|
706
749
|
$$ LANGUAGE plpgsql;
|
|
707
750
|
|
|
708
|
-
CREATE OR REPLACE FUNCTION remove_retention_archive()
|
|
751
|
+
CREATE OR REPLACE FUNCTION remove_retention_archive(enable_timescaledb BOOLEAN DEFAULT TRUE)
|
|
709
752
|
RETURNS VOID AS $$
|
|
710
753
|
BEGIN
|
|
711
|
-
|
|
754
|
+
IF enable_timescaledb THEN
|
|
755
|
+
-- Remove TimescaleDB retention policy
|
|
756
|
+
PERFORM remove_retention_policy('job_archive');
|
|
757
|
+
ELSE
|
|
758
|
+
-- Drop trigger and related objects
|
|
759
|
+
DROP TRIGGER IF EXISTS job_archive_cleanup_trigger ON job_archive;
|
|
760
|
+
DROP FUNCTION IF EXISTS cleanup_job_archive_trigger();
|
|
761
|
+
DROP SEQUENCE IF EXISTS job_archive_insert_counter;
|
|
762
|
+
END IF;
|
|
712
763
|
END;
|
|
713
764
|
$$ LANGUAGE plpgsql;
|
|
714
765
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|