Skip to content

V2.0.0#30

Closed
tavallaie wants to merge 21 commits intopgmq:mainfrom
tavallaie:v2.0.0
Closed

V2.0.0#30
tavallaie wants to merge 21 commits intopgmq:mainfrom
tavallaie:v2.0.0

Conversation

@tavallaie
Copy link
Copy Markdown
Collaborator

No description provided.

  - Create PGMQConfig dataclass for centralized configuration management
  - Implement BaseQueue with common initialization and logging setup
  - Support both legacy kwargs and modern config object patterns
  - Add backward compatibility properties for existing code
- Extract all SQL queries from inline code to dedicated module
- Add helper functions for dynamic SQL selection based on parameters
- Support all PGMQ extension features: standard ops, topics, FIFO, notifications
- Enable code reuse between sync and async implementations
…ue.py)

- Implement complete sync client using psycopg3 and ConnectionPool
- Add topic-based routing: send_topic, bind_topic, test_routing, etc.
- Add FIFO operations: read_grouped, read_grouped_rr with poll variants
- Add notification management: enable/disable/update notify
- Add batch set_vt support
- Fix pop() to properly handle qty parameter
- Maintain 100% backward compatibility with existing API
…eue.py)

- Implement async client using asyncpg with identical API to sync version
- Add init() method for async pool initialization
- Add close() method for proper cleanup
- Mirror all sync features: topics, FIFO, notifications, batch operations
- Use async_transaction decorator for transaction management
- Return AsyncPGMQueue alias in public API
- Create @transaction for sync operations with psycopg3
- Create @async_transaction for async operations with asyncpg
- Implement automatic connection injection and transaction management
- Support nested transactions via conn parameter detection
- Handle commit/rollback automatically with proper error propagation
…init__.py)

- Export SyncPGMQueue and AsyncPGMQueue with clear naming
- Maintain PGMQueue alias pointing to sync version for compatibility
- Export all message dataclasses including new types
- Keep existing decorator and logging exports
- Add __version__ for package version tracking
- Mark package as fully typed for mypy and other type checkers
- Enable better IDE autocomplete and type checking for users
- Replace complex handler removal logic with caching system
- Add LoggingManager for centralized logger creation
- Support both stdlib logging and loguru backends
- Implement structured logging with JSON output option
- Maintain backward compatible create_logger() function
- Add log_with_context for structured context logging
- Add log_performance decorator for timing
…ssages.py)

- Add missing fields: last_read_at, headers, queue_visible_length
- Create new dataclasses: QueueRecord, TopicBinding, RoutingResult, NotificationThrottle
- Add BatchTopicResult for batch topic operations
- Implement from_row factory methods for database result parsing
- Maintain backward compatibility with existing Message usage
…ibility

- Add __str__ method returning queue_name for string conversion
- Remove redundant docstring from from_row classmethod
- Ensures QueueRecord instances print queue name when converted to string
- Remove property methods that proxied `self.config` attributes
- Removed properties: host, port, database, username, password, delay, vt, pool_size, verbose, log_filename
- Clean up module comment and simplify BaseQueue class docstring
- Enforces direct access to configuration via `self.config`

BREAKING CHANGE: Accessing configuration properties directly on the queue instance (e.g., `queue.host`) is no longer supported. Use `queue.config.host` instead.
…patibility

Major Changes:
- Add _convert_sql() to convert psycopg %s placeholders to asyncpg , ... style
- Integrate orjson for explicit JSON/JSONB serialization in all message operations
- Implement __post_init__ to build PGMQConfig from backward-compatible fields
- Apply explicit type casting for vt parameter to resolve ambiguous function errors

Backward Compatibility:
- Preserve direct field access (host, port, database, etc.) on PGMQueue instance
- Add tz parameter as alias for delay in send operations
- Add read_batch() method as alias for read() with batch_size
- Emit UserWarning when list_queues() returns QueueRecord objects instead of strings
- Maintain existing method signatures where possible

Code Quality:
- Remove redundant module comment lines
- Update docstrings with version change notes
- Improve validate_queue_name() to raise exception on invalid names
- Clean up imports and organize field definitions

BREAKING CHANGE: list_queues() now returns List[QueueRecord] instead of List[str].
Access queue names via the .queue_name attribute. A deprecation warning is emitted.

BREAKING CHANGE: Configuration should be accessed via queue.config.* instead of
queue.* directly. Direct field access is maintained for backward compatibility
but may be removed in future versions.
Major Changes:
- Implement __post_init__ to build PGMQConfig from backward-compatible fields
- Preserve direct field access (host, port, database, etc.) on PGMQueue instance
- Simplify all method docstrings by removing verbose Args/Returns sections
- Add read_batch() method as backward compatibility alias for read()

Backward Compatibility:
- Add tz parameter as alias for delay in send operations
- Add read_batch() method as alias for read() with batch_size parameter
- Emit UserWarning when list_queues() returns QueueRecord objects instead of strings
- Maintain existing method signatures where possible
- Keep deprecated detach_archive() method as no-op for compatibility

Code Quality:
- Remove redundant module comment line
- Update list_queues() docstring with version change notes
- Improve validate_queue_name() to raise exception on invalid names
- Clean up imports and organize field definitions with clear sections
- Standardize docstring format across all public methods

Breaking Changes:
- list_queues() now returns List[QueueRecord] instead of List[str].
  Access queue names via the .queue_name attribute. A deprecation warning is emitted.

- Configuration should be accessed via queue.config.* instead of queue.* directly.
  Direct field access is maintained for backward compatibility but may be removed
  in future versions.

- validate_queue_name() now raises exception on invalid names instead of
  returning False. Update error handling accordingly.
…urn type

Test Reorganization:
- Create tests/backward_compatibility/v1/ directory structure
- Move test_integration.py to tests/backward_compatibility/v1/test_integration.py
- Move test_async_integration.py to tests/backward_compatibility/v1/test_async_integration.py
- Add __init__.py files for proper package structure

Test Updates:
- Update test_list_queues() in test_integration.py to handle QueueRecord objects
- Update test_list_queues() in test_async_integration.py to handle QueueRecord objects
- Change assertions from string comparison to .queue_name attribute access
- Add comments documenting the return type change

Backward Compatibility:
- Tests now reflect v2.0.0 API where list_queues() returns List[QueueRecord]
- Preserves test coverage for core queue operations under v1 compatibility folder
…cations

Add SyncNotificationListener and AsyncNotificationListener classes to enable
real-time message notifications using PostgreSQL's NOTIFY/LISTEN mechanism.

- SyncNotificationListener: blocking listener using psycopg3 with graceful
  shutdown via connection close to interrupt notifies() generator
- AsyncNotificationListener: asyncio-based listener using asyncpg with
  internal queue for callback dispatch
- Both support channel naming convention `pgmq_insert_{queue_name}`
- Includes comprehensive test suite for logging isolation and features
…-queue-notifications

feat: add PostgreSQL LISTEN/NOTIFY support for real-time queue notifications
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @tavallaie, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request represents a major version update for the PGMQ Python client, focusing on a comprehensive architectural refactor to enhance modularity, maintainability, and extensibility. It centralizes SQL operations, introduces a robust configuration and logging system, and expands the client's capabilities to fully leverage advanced PGMQ extension features like topic routing and notifications. The changes aim to provide a more stable, type-safe, and feature-rich experience for developers.

Highlights

  • Architectural Refactoring: The client codebase has undergone a significant refactoring, introducing a new BaseQueue class for shared logic and a PGMQConfig dataclass to centralize configuration across synchronous and asynchronous clients. This improves modularity and maintainability.
  • Centralized SQL Queries: All SQL query strings have been moved into a new _sql.py module, ensuring consistency between sync and async implementations and simplifying query management.
  • Enhanced Data Models and Type Hinting: New dataclasses have been added in messages.py for QueueRecord, TopicBinding, RoutingResult, BatchTopicResult, and NotificationThrottle, providing richer type hints and better representation of PGMQ's database types.
  • Improved Logging System: The logging infrastructure has been overhauled with a new LoggingManager (formerly PGMQLogger), offering better isolation, idempotent configuration, and structured logging capabilities, while maintaining backward compatibility.
  • New Notification Listeners: Dedicated SyncNotificationListener and AsyncNotificationListener classes have been introduced to facilitate real-time event processing via PostgreSQL's NOTIFY mechanism.
  • Expanded Client Functionality: Both PGMQueue (sync) and AsyncPGMQueue now support new PGMQ extension features including topic-based routing, conditional message reading, FIFO grouped reads, and notification management.
  • Test Suite Reorganization: The test suite has been reorganized into backward_compatibility/v1 and v2 directories, ensuring existing functionality remains stable while new features are thoroughly tested.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .gitignore
    • Added '.python-version' to the ignore list.
  • Makefile
    • Updated the PostgreSQL Docker image to 'pg18-pgmq:v1.10.0'.
  • src/pgmq/init.py
    • Refactored imports to expose new client classes ('SyncPGMQueue', 'AsyncPGMQueue') and expanded data types.
    • Updated the package version to '0.5.0'.
  • src/pgmq/_sql.py
    • Added a new module to centralize all SQL query strings and helper functions for constructing them.
  • src/pgmq/async_queue.py
    • Refactored the asynchronous PGMQ client to inherit from 'BaseQueue' for shared logic.
    • Integrated the new '_sql.py' module for all database operations.
    • Updated logging to use the new 'LoggingManager' and 'log_with_context'.
    • Introduced new methods for topic routing, FIFO operations, conditional reads, and notification management.
  • src/pgmq/base.py
    • Added a new base class 'BaseQueue' to provide common initialization and utilities for PGMQ clients.
    • Added a 'PGMQConfig' dataclass for shared configuration parameters.
  • src/pgmq/decorators.py
    • Refactored transaction decorators to be more robust and type-hinted, ensuring proper connection management.
  • src/pgmq/logger.py
    • Renamed 'PGMQLogger' to 'LoggingManager' for clarity and refactored logging setup for better isolation and caching.
    • Updated 'log_performance' decorator to handle both synchronous and asynchronous functions.
  • src/pgmq/messages.py
    • Expanded with new dataclasses for 'QueueRecord', 'TopicBinding', 'RoutingResult', 'BatchTopicResult', and 'NotificationThrottle'.
    • Updated 'Message' and 'QueueMetrics' dataclasses with 'from_row' factory methods for easier instantiation from database results.
  • src/pgmq/notify_listener.py
    • Added a new module implementing 'SyncNotificationListener' and 'AsyncNotificationListener' for PGMQ notifications.
  • src/pgmq/py.typed
    • Modified the marker file for PEP 561.
  • src/pgmq/queue.py
    • Refactored the synchronous PGMQ client to inherit from 'BaseQueue' for shared logic.
    • Integrated the new '_sql.py' module for all database operations.
    • Updated logging to use the new 'LoggingManager' and 'log_with_context'.
    • Introduced new methods for topic routing, FIFO operations, conditional reads, and notification management.
  • tests/backward_compatibility/v1/test_async_integration.py
    • Renamed from 'tests/test_async_integration.py' to reflect V1 backward compatibility.
    • Updated 'test_list_queues' to assert against the '.queue_name' attribute of 'QueueRecord' objects.
  • tests/backward_compatibility/v1/test_integration.py
    • Renamed from 'tests/test_integration.py' to reflect V1 backward compatibility.
    • Updated 'test_list_queues' to assert against the '.queue_name' attribute of 'QueueRecord' objects.
  • tests/v2/test_async_queue.py
    • Added new tests for asynchronous PGMQ client V2 features, including headers, conditional reads, and FIFO methods.
  • tests/v2/test_features.py
    • Added new tests for PGMQ V2 features, specifically covering partitioning, notifications, and validator functions.
  • tests/v2/test_logger.py
    • Added new tests for logging isolation, structured logging, and performance decorators.
  • tests/v2/test_notify_listener.py
    • Added new tests for synchronous and asynchronous notification listeners.
  • tests/v2/test_routing.py
    • Added new tests for PGMQ V2 topic routing features, including binding, unbinding, sending, and testing routes.
  • tests/v2/test_sync_queue.py
    • Added new tests for synchronous PGMQ client V2 features, including headers, conditional reads, and FIFO methods.
  • tests/v2/utils.py
    • Added a new utility file for V2 test setup and common test helpers.
  • uv.lock
    • Updated the 'pgmq' package version to '1.0.5'.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a major refactoring for version 2.0.0, significantly improving the library's structure, features, and maintainability. Key improvements include a unified configuration model, centralized SQL queries, the introduction of a base class for sync and async clients, and a complete overhaul of the logging system. The addition of new features like topic-based routing, FIFO queues, and notification listeners is well-supported by an extensive new test suite. The code is much clearer and more robust. I've included a couple of suggestions to further improve the robustness of SQL queries, particularly for overloaded functions, to prevent potential ambiguity and enhance maintainability.

Comment thread src/pgmq/_sql.py
Comment on lines +49 to +67
SEND_TOPIC = "SELECT pgmq.send_topic(%s::text, %s::jsonb);"
SEND_TOPIC_WITH_HEADERS = "SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::jsonb);"
SEND_TOPIC_WITH_DELAY_INT = "SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::integer);"
SEND_TOPIC_WITH_HEADERS_DELAY_INT = (
"SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::jsonb, %s::integer);"
)

SEND_BATCH_TOPIC = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[]);"
SEND_BATCH_TOPIC_WITH_HEADERS = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[]);"
)
SEND_BATCH_TOPIC_WITH_DELAY_INT = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::integer);"
)
SEND_BATCH_TOPIC_WITH_DELAY_TZ = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::timestamptz);"
)
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_INT = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[], %s::integer);"
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_TZ = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[], %s::timestamptz);"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The queries for send_topic and send_batch_topic use positional arguments. This is inconsistent with the non-topic send functions that use named arguments (e.g., queue_name=>..., msg=>...). Using named arguments for all function calls improves readability and makes the code more robust against changes in parameter order or function overloads.

Suggested change
SEND_TOPIC = "SELECT pgmq.send_topic(%s::text, %s::jsonb);"
SEND_TOPIC_WITH_HEADERS = "SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::jsonb);"
SEND_TOPIC_WITH_DELAY_INT = "SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::integer);"
SEND_TOPIC_WITH_HEADERS_DELAY_INT = (
"SELECT pgmq.send_topic(%s::text, %s::jsonb, %s::jsonb, %s::integer);"
)
SEND_BATCH_TOPIC = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[]);"
SEND_BATCH_TOPIC_WITH_HEADERS = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[]);"
)
SEND_BATCH_TOPIC_WITH_DELAY_INT = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::integer);"
)
SEND_BATCH_TOPIC_WITH_DELAY_TZ = (
"SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::timestamptz);"
)
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_INT = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[], %s::integer);"
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_TZ = "SELECT * FROM pgmq.send_batch_topic(%s::text, %s::jsonb[], %s::jsonb[], %s::timestamptz);"
SEND_TOPIC = "SELECT pgmq.send_topic(routing_key=>%s::text, msg=>%s::jsonb);"
SEND_TOPIC_WITH_HEADERS = "SELECT pgmq.send_topic(routing_key=>%s::text, msg=>%s::jsonb, headers=>%s::jsonb);"
SEND_TOPIC_WITH_DELAY_INT = "SELECT pgmq.send_topic(routing_key=>%s::text, msg=>%s::jsonb, delay=>%s::integer);"
SEND_TOPIC_WITH_HEADERS_DELAY_INT = (
"SELECT pgmq.send_topic(routing_key=>%s::text, msg=>%s::jsonb, headers=>%s::jsonb, delay=>%s::integer);"
)
SEND_BATCH_TOPIC = "SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[]);"
SEND_BATCH_TOPIC_WITH_HEADERS = (
"SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[], headers=>%s::jsonb[]);"
)
SEND_BATCH_TOPIC_WITH_DELAY_INT = (
"SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[], delay=>%s::integer);"
)
SEND_BATCH_TOPIC_WITH_DELAY_TZ = (
"SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[], delay=>%s::timestamptz);"
)
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_INT = "SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[], headers=>%s::jsonb[], delay=>%s::integer);"
SEND_BATCH_TOPIC_WITH_HEADERS_DELAY_TZ = "SELECT * FROM pgmq.send_batch_topic(routing_key=>%s::text, msgs=>%s::jsonb[], headers=>%s::jsonb[], delay=>%s::timestamptz);"

Comment thread src/pgmq/_sql.py
Comment on lines +134 to +138
SET_VT = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_id=>%s::bigint, vt=>%s);"""

SET_VT_BATCH = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_ids=>%s::bigint[], vt=>%s);"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The set_vt function in PostgreSQL is overloaded to accept either an integer or a timestamptz for the visibility timeout. Relying on a single SQL string for both can lead to ambiguity and requires fragile string manipulation in the client code. To make the queries more robust and explicit, it's better to define separate constants for each type. The client code in queue.py and async_queue.py will need to be updated to select the appropriate constant.

Suggested change
SET_VT = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_id=>%s::bigint, vt=>%s);"""
SET_VT_BATCH = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_ids=>%s::bigint[], vt=>%s);"""
SET_VT_INT = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_id=>%s::bigint, vt=>%s::integer);"""
SET_VT_TZ = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_id=>%s::bigint, vt=>%s::timestamptz);"""
SET_VT_BATCH_INT = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_ids=>%s::bigint[], vt=>%s::integer);"""
SET_VT_BATCH_TZ = """SELECT msg_id, read_ct, enqueued_at, last_read_at, vt, message, headers
FROM pgmq.set_vt(queue_name=>%s::text, msg_ids=>%s::bigint[], vt=>%s::timestamptz);"""

@tavallaie tavallaie closed this Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant