Add FastAPI pub/sub example with real-world PGMQ usage patterns#32
Add FastAPI pub/sub example with real-world PGMQ usage patterns#32jason810496 merged 18 commits intomainfrom
Conversation
Co-authored-by: jason810496 <[email protected]>
Co-authored-by: jason810496 <[email protected]>
Co-authored-by: jason810496 <[email protected]>
Co-authored-by: jason810496 <[email protected]>
Co-authored-by: jason810496 <[email protected]>
629f070 to
0a6d22e
Compare
jason810496
left a comment
There was a problem hiding this comment.
@copilot Resolve all the review comments
| # Create order in database | ||
| db_order = Order( | ||
| customer_name=order_data.customer_name, | ||
| product_name=order_data.product_name, | ||
| quantity=order_data.quantity, | ||
| price=order_data.price, | ||
| ) | ||
| db.add(db_order) | ||
| db.commit() | ||
| db.refresh(db_order) | ||
|
|
||
| # Publish message to PGMQ using op | ||
| message_data = { | ||
| "order_id": db_order.id, | ||
| "customer_name": db_order.customer_name, | ||
| "product_name": db_order.product_name, | ||
| "quantity": db_order.quantity, | ||
| "price": db_order.price, | ||
| "created_at": db_order.created_at.isoformat(), | ||
| } | ||
|
|
||
| msg_id = op.send(QUEUE_NAME, message_data, session=db, commit=True) |
There was a problem hiding this comment.
The op.send should be in the same transaction as creating order.
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Now using db.flush() to get the order ID, then op.send() with commit=False, and finally db.commit() to commit both operations in the same transaction.
| batch_size: Number of messages to read in each batch | ||
| vt: Visibility timeout in seconds | ||
| """ | ||
| from pgmq_sqlalchemy import op |
There was a problem hiding this comment.
We should use method of pgmq instance instead of using op in this demo.
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Updated to use pgmq.read_batch_async() instead of op.read_batch_async().
| # Read a batch of messages | ||
| async with pgmq.session_maker() as session: | ||
| messages = await op.read_batch_async(QUEUE_NAME, vt=vt, batch_size=batch_size, session=session, commit=True) |
There was a problem hiding this comment.
Use the method of pgmq instance directly.
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Updated to use pgmq.delete_async() instead of op.delete_async().
| # Note: Manual PGMQueue setup to avoid event loop conflicts | ||
| # PGMQueue.__init__ tries to run a nested event loop which conflicts | ||
| # with asyncio.run(). This is a known limitation when using PGMQueue | ||
| # in an async context manager like asyncio.run(). | ||
| # For proper usage, consider using PGMQOperation methods directly with sessions. | ||
| pgmq = PGMQueue.__new__(PGMQueue) | ||
| pgmq.engine = async_engine | ||
| pgmq.session_maker = async_session_maker | ||
| pgmq.is_async = True | ||
| pgmq.delay = 0 | ||
| pgmq.vt = 30 | ||
| pgmq.loop = None | ||
| pgmq.is_pg_partman_ext_checked = True |
There was a problem hiding this comment.
Check the constructor of PGMQueue again. We can now provide event loop to the PGMQueue.
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Now properly initializing PGMQueue with loop=asyncio.get_event_loop() to avoid event loop conflicts.
| # Check PGMQ extension manually | ||
| async with async_session_maker() as session: | ||
| from pgmq_sqlalchemy import op | ||
| await op.check_pgmq_ext_async(session=session, commit=True) |
There was a problem hiding this comment.
We don't need to check by op, use method of pgmq instance
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Removed manual extension check with op - now handled automatically by PGMQueue constructor.
| ## Installation | ||
|
|
||
| Install required dependencies: | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` |
There was a problem hiding this comment.
Use uv to manage the dependencies instead of requirements.txt
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Updated README to use uv pip install commands instead of pip with requirements.txt.
| if not order: | ||
| raise HTTPException(status_code=404, detail="Order not found") | ||
| return order | ||
|
|
There was a problem hiding this comment.
Let's add a route to query the msg from pgmq.
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Added GET /messages?limit=10 endpoint to query messages from the PGMQ queue.
|
|
||
| from pgmq_sqlalchemy import PGMQueue | ||
|
|
||
|
|
There was a problem hiding this comment.
place this file under examples_tests/unit
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Moved test file to examples_tests/unit/test_fastapi_pub_sub.py.
| from sqlalchemy.orm import sessionmaker | ||
|
|
||
| from pgmq_sqlalchemy import PGMQueue | ||
|
|
There was a problem hiding this comment.
Add another test under examples_tests/integration
api_instancefixture, module level, auto use, that use subprocess to spin up theapi.pyand should teardown the subprocess when finish.consumer_instancefixture, module level, auto use, and that use subprocess to spin up theconsumer.pyand should teardown the subprocess when finish.- The test should create 100 order parallely by calling api
- The test should wait until consumer process the all the orders
There was a problem hiding this comment.
Fixed in commit 0d2e4bf. Added examples_tests/integration/test_fastapi_integration.py with:
api_instancefixture that spins up API server subprocessconsumer_instancefixture that spins up consumer subprocess- Test that creates 100 orders in parallel via the API
- Test waits for consumer to process all messages and verifies queue is empty
…ges endpoint, reorganize tests Co-authored-by: jason810496 <[email protected]>
… imports Co-authored-by: jason810496 <[email protected]>
Codecov Report✅ All modified and coverable lines are covered by tests. 🚀 New features to boost your workflow:
|
| async with pgmq.session_maker() as session: | ||
| messages = await pgmq.read_batch_async(QUEUE_NAME, vt=vt, batch_size=batch_size, session=session, commit=True) |
There was a problem hiding this comment.
| async with pgmq.session_maker() as session: | |
| messages = await pgmq.read_batch_async(QUEUE_NAME, vt=vt, batch_size=batch_size, session=session, commit=True) | |
| messages = pgmq.read_batch(QUEUE_NAME, vt=vt, batch_size=batch_size) |
There was a problem hiding this comment.
Fixed in commit 01992d8. Simplified to use messages = await pgmq.read_batch(QUEUE_NAME, vt=vt, batch_size=batch_size) without explicit session management.
| async with pgmq.session_maker() as session: | ||
| deleted = await pgmq.delete_async(QUEUE_NAME, msg_id, session=session, commit=True) |
There was a problem hiding this comment.
| async with pgmq.session_maker() as session: | |
| deleted = await pgmq.delete_async(QUEUE_NAME, msg_id, session=session, commit=True) | |
| deleted = await pgmq.delete(QUEUE_NAME, msg_id) |
There was a problem hiding this comment.
Fixed in commit 01992d8. Simplified to use deleted = await pgmq.delete(QUEUE_NAME, msg_id) without explicit session management.
| fastapi>=0.104.0 | ||
| uvicorn>=0.24.0 | ||
| psycopg2-binary>=2.9.9 | ||
| asyncpg>=0.29.0 | ||
| pgmq-sqlalchemy>=0.1.2 |
There was a problem hiding this comment.
Add a uv based pyproject.toml under examples/fastapi_pub_sub
There was a problem hiding this comment.
Fixed in commit 01992d8. Added examples/fastapi_pub_sub/pyproject.toml with project metadata and dependencies for uv-based dependency management. Also updated README with installation instructions.
|
|
||
| - name: Run examples tests | ||
| run: | | ||
| uv run pytest examples_tests --cov=examples --cov-report=xml:coverage-examples-py${{ matrix.python-version }}.xml -v |
There was a problem hiding this comment.
| uv run pytest examples_tests --cov=examples --cov-report=xml:coverage-examples-py${{ matrix.python-version }}.xml -v | |
| uv run pytest examples_tests --cov=examples --cov-report=xml:coverage-examples-py${{ matrix.python-version }}.xml -vss |
There was a problem hiding this comment.
Fixed in commit 71a5868. Updated pytest flags to -vss for better test output visibility in CI.
Co-authored-by: jason810496 <[email protected]>
Co-authored-by: jason810496 <[email protected]>
Distinguish sync and async operations in PGMQueue - Introduced a pre-commit hook to check for missing async methods in PGMQueue. - Added scripts to identify and generate missing async methods. - Created utility functions for AST manipulation and method transformation. - Established configuration for project paths and console output.
d6fc36e to
20e2fba
Compare
- Update FastAPI API to improve response models and logging. - Introduce create_orders_coordinator.py for parallel order creation. - Enhance consumer.py with verbose logging and improved error handling. - Modify integration tests to support new features and improve readability.
Description
Adds a production-ready FastAPI example demonstrating PGMQ integration for order management with separate producer and consumer processes.
Implementation
examples/fastapi_pub_sub/api.py: FastAPI server using sync operations (psycopg2) withPGMQOperation(imported asop) for message publishing in request handlers/messagesendpoint to query messages from PGMQ queueDATABASE_URLandQUEUE_NAMEexamples/fastapi_pub_sub/consumer.py: Async worker using asyncpg withPGMQueueinstance methods for concurrent message processingpgmq.read_batch()andpgmq.delete()methods (no explicit session management needed)DATABASE_URLandQUEUE_NAMEexamples/fastapi_pub_sub/README.md: Setup and usage documentation withuvinstallation instructionsexamples/fastapi_pub_sub/pyproject.toml: Project configuration for uv-based dependency managementTesting
examples_tests/unit/: Unit test suite covering API endpoints, message publishing, and async consumer processingexamples_tests/integration/: Integration test suite with subprocess-based tests.github/workflows/examples.yml: CI workflow triggered by changes toexamples/,examples_tests/, orpgmq_sqlalchemy/on Python 3.9-3.12-vsspytest flags for better test output visibility in CIExample Usage
Notes
PGMQueueinstance with proper event loop initialization and simplified method callson_event)Status
Checklist
pre-commitwithruff)Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.