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.
Files changed (53) hide show
  1. {queuerpy-0.19.0/queuerPy.egg-info → queuerpy-0.20.0}/PKG-INFO +1 -1
  2. {queuerpy-0.19.0 → queuerpy-0.20.0}/_version.py +1 -1
  3. {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_job.py +25 -12
  4. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/database.py +5 -0
  5. {queuerpy-0.19.0 → queuerpy-0.20.0}/pyproject.toml +2 -2
  6. {queuerpy-0.19.0 → queuerpy-0.20.0/queuerPy.egg-info}/PKG-INFO +1 -1
  7. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_global.py +1 -1
  8. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_master.py +25 -14
  9. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_worker.py +13 -0
  10. {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/job.sql +58 -7
  11. {queuerpy-0.19.0 → queuerpy-0.20.0}/LICENSE +0 -0
  12. {queuerpy-0.19.0 → queuerpy-0.20.0}/MANIFEST.in +0 -0
  13. {queuerpy-0.19.0 → queuerpy-0.20.0}/README.md +0 -0
  14. {queuerpy-0.19.0 → queuerpy-0.20.0}/__init__.py +0 -0
  15. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/__init__.py +0 -0
  16. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/broadcaster.py +0 -0
  17. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/listener.py +0 -0
  18. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/retryer.py +0 -0
  19. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/runner.py +0 -0
  20. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/scheduler.py +0 -0
  21. {queuerpy-0.19.0 → queuerpy-0.20.0}/core/ticker.py +0 -0
  22. {queuerpy-0.19.0 → queuerpy-0.20.0}/database/__init__.py +0 -0
  23. {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_listener.py +0 -0
  24. {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_master.py +0 -0
  25. {queuerpy-0.19.0 → queuerpy-0.20.0}/database/db_worker.py +0 -0
  26. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/__init__.py +0 -0
  27. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/error.py +0 -0
  28. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/logging.py +0 -0
  29. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/sql.py +0 -0
  30. {queuerpy-0.19.0 → queuerpy-0.20.0}/helper/task.py +0 -0
  31. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/__init__.py +0 -0
  32. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/batch_job.py +0 -0
  33. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/connection.py +0 -0
  34. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/job.py +0 -0
  35. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/master.py +0 -0
  36. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/options.py +0 -0
  37. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/options_on_error.py +0 -0
  38. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/task.py +0 -0
  39. {queuerpy-0.19.0 → queuerpy-0.20.0}/model/worker.py +0 -0
  40. {queuerpy-0.19.0 → queuerpy-0.20.0}/py.typed +0 -0
  41. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer.py +0 -0
  42. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/SOURCES.txt +0 -0
  43. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/dependency_links.txt +0 -0
  44. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/requires.txt +0 -0
  45. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuerPy.egg-info/top_level.txt +0 -0
  46. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_job.py +0 -0
  47. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_listener.py +0 -0
  48. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_next_interval.py +0 -0
  49. {queuerpy-0.19.0 → queuerpy-0.20.0}/queuer_task.py +0 -0
  50. {queuerpy-0.19.0 → queuerpy-0.20.0}/setup.cfg +0 -0
  51. {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/master.sql +0 -0
  52. {queuerpy-0.19.0 → queuerpy-0.20.0}/sql/notify.sql +0 -0
  53. {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.19.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>
@@ -1,6 +1,6 @@
1
1
  """Version information for queuerPy package."""
2
2
 
3
- __version__ = "0.19.0"
3
+ __version__ = "0.20.0"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # For compatibility
@@ -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
- with_table_drop: bool = False,
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
- with self.db.instance.cursor() as cur:
383
- cur.execute("SELECT add_retention_archive(%s);", (days,))
384
- self.db.instance.commit()
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
- with self.db.instance.cursor() as cur:
395
- cur.execute("SELECT remove_retention_archive();")
396
- self.db.instance.commit()
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.19.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.19.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.19.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.with_table_drop, encryption_key
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 old_master.settings.retention_archive == 0:
42
- self.db_job.add_retention_archive(master_settings.retention_archive)
43
- elif (
44
- old_master.settings.retention_archive
45
- != master_settings.retention_archive
46
- ):
47
- self.db_job.remove_retention_archive()
48
- self.db_job.add_retention_archive(master_settings.retention_archive)
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
- func=ticker_task,
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
- func=ticker_task,
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
- CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
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
- PERFORM create_hypertable('job_archive', by_range('updated_at'), if_not_exists => TRUE);
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
- PERFORM add_retention_policy('job_archive', (input_retention_days * INTERVAL '1 day'));
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
- PERFORM remove_retention_policy('job_archive');
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