ry-pg-utils 1.0.2__tar.gz → 1.0.3__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.
- {ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info → ry_pg_utils-1.0.3}/PKG-INFO +263 -77
- ry_pg_utils-1.0.3/README.md +628 -0
- ry_pg_utils-1.0.3/VERSION +1 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/ipc/channels.py +0 -2
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3/src/ry_pg_utils.egg-info}/PKG-INFO +263 -77
- ry_pg_utils-1.0.2/README.md +0 -442
- ry_pg_utils-1.0.2/VERSION +0 -1
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/LICENSE +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/MANIFEST.in +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/packages/base_requirements.in +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/pyproject.toml +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/setup.cfg +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/setup.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/__init__.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/config.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/connect.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/dynamic_table.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/ipc/__init__.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/notify_trigger.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/parse_args.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/pb_types/__init__.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/pb_types/database_pb2.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/pb_types/database_pb2.pyi +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/pb_types/py.typed +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/postgres_info.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/py.typed +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils/updater.py +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils.egg-info/SOURCES.txt +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils.egg-info/dependency_links.txt +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils.egg-info/requires.txt +0 -0
- {ry_pg_utils-1.0.2 → ry_pg_utils-1.0.3}/src/ry_pg_utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ry-pg-utils
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.3
|
4
4
|
Summary: Utility functions for PostgreSQL
|
5
5
|
Author: Ross Yeager
|
6
6
|
Author-email: ryeager12@email.com
|
@@ -31,7 +31,7 @@ Dynamic: summary
|
|
31
31
|
|
32
32
|
# ry-pg-utils
|
33
33
|
|
34
|
-
A Python utility library for PostgreSQL database operations with dynamic table creation, connection management,
|
34
|
+
A Python utility library for PostgreSQL database operations with dynamic table creation, connection management, Protocol Buffer integration, and PostgreSQL LISTEN/NOTIFY support.
|
35
35
|
|
36
36
|
## Overview
|
37
37
|
|
@@ -41,16 +41,19 @@ A Python utility library for PostgreSQL database operations with dynamic table c
|
|
41
41
|
- Dynamic table creation from Protocol Buffer message definitions
|
42
42
|
- Thread-safe session management
|
43
43
|
- Multi-backend support with automatic backend ID tracking
|
44
|
-
-
|
44
|
+
- PostgreSQL LISTEN/NOTIFY triggers and notifications
|
45
|
+
- Database updater for dynamic configuration via Redis
|
45
46
|
- Argument parsing for PostgreSQL connection parameters
|
46
47
|
|
47
48
|
## Features
|
48
49
|
|
49
|
-
- **Connection Management**: Thread-safe PostgreSQL connection pooling with automatic retry logic
|
50
|
+
- **Connection Management**: Thread-safe PostgreSQL connection pooling with automatic retry logic and health checks
|
50
51
|
- **Dynamic Tables**: Automatically create and manage database tables from Protocol Buffer message schemas
|
51
52
|
- **Multi-Backend Support**: Track data across multiple backend instances with automatic ID tagging
|
52
53
|
- **Session Management**: Context managers for safe database session handling
|
54
|
+
- **Notification System**: Built-in PostgreSQL LISTEN/NOTIFY support with triggers and callbacks
|
53
55
|
- **Configuration System**: Flexible configuration via environment variables and runtime settings
|
56
|
+
- **Redis Integration**: Optional Redis-based database configuration updates
|
54
57
|
- **Type Safety**: Full type hints and mypy support
|
55
58
|
|
56
59
|
## Installation
|
@@ -63,8 +66,13 @@ pip install ry-pg-utils
|
|
63
66
|
|
64
67
|
- Python 3.12+
|
65
68
|
- PostgreSQL database
|
66
|
-
- SQLAlchemy
|
67
|
-
- Protocol Buffer support
|
69
|
+
- SQLAlchemy 2.0+
|
70
|
+
- Protocol Buffer support (protobuf)
|
71
|
+
- psycopg2-binary
|
72
|
+
- tenacity (for retry logic)
|
73
|
+
- python-dotenv (for environment variables)
|
74
|
+
- ryutils (logging utilities)
|
75
|
+
- ry_redis_bus (optional, for Redis integration)
|
68
76
|
|
69
77
|
## Configuration
|
70
78
|
|
@@ -105,11 +113,11 @@ pg_config.backend_id = "custom_backend_id"
|
|
105
113
|
| `postgres_db` | str | From env | Database name |
|
106
114
|
| `postgres_user` | str | From env | Database username |
|
107
115
|
| `postgres_password` | str | From env | Database password |
|
108
|
-
| `backend_id` | str | hostname_ip | Unique identifier for this backend instance |
|
116
|
+
| `backend_id` | str | POSTGRES_USER or hostname_ip | Unique identifier for this backend instance |
|
109
117
|
| `add_backend_to_all` | bool | True | Add backend_id column to all tables |
|
110
118
|
| `add_backend_to_tables` | bool | True | Append backend_id to table names |
|
111
119
|
| `raise_on_use_before_init` | bool | True | Raise exception if DB used before initialization |
|
112
|
-
| `do_publish_db` | bool | False | Enable database publishing features |
|
120
|
+
| `do_publish_db` | bool | False | Enable database publishing features (for Redis integration) |
|
113
121
|
| `use_local_db_only` | bool | True | Use only local database connections |
|
114
122
|
|
115
123
|
## Quick Start
|
@@ -118,19 +126,15 @@ pg_config.backend_id = "custom_backend_id"
|
|
118
126
|
|
119
127
|
```python
|
120
128
|
from ry_pg_utils.connect import init_database, ManagedSession
|
121
|
-
from ry_pg_utils.postgres_info import PostgresInfo
|
122
129
|
|
123
|
-
#
|
124
|
-
|
130
|
+
# Initialize the database connection
|
131
|
+
init_database(
|
125
132
|
db_name="myapp_db",
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
133
|
+
db_host="localhost",
|
134
|
+
db_port=5432,
|
135
|
+
db_user="postgres",
|
136
|
+
db_password="secret"
|
130
137
|
)
|
131
|
-
|
132
|
-
# Initialize the database connection
|
133
|
-
init_database(db_info, db_name="myapp")
|
134
138
|
```
|
135
139
|
|
136
140
|
### 2. Use Dynamic Tables with Protocol Buffers
|
@@ -165,12 +169,14 @@ exists = DynamicTableDb.is_in_db(
|
|
165
169
|
|
166
170
|
```python
|
167
171
|
from ry_pg_utils.connect import ManagedSession
|
172
|
+
from sqlalchemy import text
|
168
173
|
|
169
174
|
# Use context manager for automatic session cleanup
|
170
|
-
with ManagedSession(db="
|
171
|
-
|
172
|
-
|
173
|
-
|
175
|
+
with ManagedSession(db="myapp_db") as session:
|
176
|
+
if session:
|
177
|
+
result = session.execute(text("SELECT * FROM my_table"))
|
178
|
+
for row in result:
|
179
|
+
print(row)
|
174
180
|
```
|
175
181
|
|
176
182
|
## Core Components
|
@@ -181,21 +187,28 @@ The connection module provides thread-safe database connection and session manag
|
|
181
187
|
|
182
188
|
```python
|
183
189
|
from ry_pg_utils.connect import (
|
184
|
-
init_database,
|
185
|
-
init_engine,
|
186
|
-
ManagedSession,
|
187
|
-
get_backend_id,
|
188
|
-
set_backend_id,
|
189
|
-
|
190
|
-
|
190
|
+
init_database, # Initialize database connection
|
191
|
+
init_engine, # Initialize SQLAlchemy engine
|
192
|
+
ManagedSession, # Context manager for sessions
|
193
|
+
get_backend_id, # Get current backend ID
|
194
|
+
set_backend_id, # Set backend ID for thread
|
195
|
+
get_engine, # Get engine for database
|
196
|
+
close_engine, # Close database connection
|
197
|
+
clear_db, # Clear all connections
|
198
|
+
get_table_name, # Get table name with backend suffix
|
199
|
+
is_database_initialized, # Check if database is initialized
|
200
|
+
Base, # SQLAlchemy declarative base
|
191
201
|
)
|
192
202
|
```
|
193
203
|
|
194
204
|
**Key Features:**
|
195
205
|
- Thread-local backend ID tracking
|
196
|
-
- Connection pooling with configurable parameters
|
197
|
-
- Automatic connection recovery
|
206
|
+
- Connection pooling with configurable parameters (pool_size, max_overflow, pool_recycle)
|
207
|
+
- Automatic connection recovery with retry logic (using tenacity)
|
198
208
|
- Session scoping for thread safety
|
209
|
+
- Automatic backend_id injection before flush operations
|
210
|
+
- Pre-ping health checks to validate connections
|
211
|
+
- Support for auto-importing model modules
|
199
212
|
|
200
213
|
### `dynamic_table.py` - Dynamic Table Creation
|
201
214
|
|
@@ -234,7 +247,7 @@ exists = db.inst_is_in_db(
|
|
234
247
|
|
235
248
|
### `postgres_info.py` - Connection Information
|
236
249
|
|
237
|
-
|
250
|
+
Class for PostgreSQL connection parameters:
|
238
251
|
|
239
252
|
```python
|
240
253
|
from ry_pg_utils.postgres_info import PostgresInfo
|
@@ -247,8 +260,12 @@ db_info = PostgresInfo(
|
|
247
260
|
password="secret"
|
248
261
|
)
|
249
262
|
|
250
|
-
#
|
251
|
-
|
263
|
+
# Check if valid
|
264
|
+
if not db_info.is_null():
|
265
|
+
print(db_info) # Prints info with password masked
|
266
|
+
|
267
|
+
# Create null instance
|
268
|
+
null_info = PostgresInfo.null()
|
252
269
|
```
|
253
270
|
|
254
271
|
### `parse_args.py` - Argument Parsing
|
@@ -268,48 +285,129 @@ args = parser.parse_args()
|
|
268
285
|
|
269
286
|
### `updater.py` - Database Configuration Updater
|
270
287
|
|
271
|
-
Dynamically update database connections based on configuration messages:
|
288
|
+
Dynamically update database connections based on configuration messages via Redis:
|
272
289
|
|
273
290
|
```python
|
274
|
-
from ry_pg_utils.updater import
|
291
|
+
from ry_pg_utils.updater import DbUpdater
|
292
|
+
from ry_redis_bus.helpers import RedisInfo
|
293
|
+
from ryutils.verbose import Verbose
|
275
294
|
|
276
|
-
updater =
|
295
|
+
updater = DbUpdater(
|
277
296
|
redis_info=redis_info,
|
278
|
-
|
279
|
-
backend_id="my_backend"
|
297
|
+
args=args, # argparse.Namespace with postgres config
|
298
|
+
backend_id="my_backend",
|
299
|
+
verbose=Verbose(True)
|
300
|
+
)
|
301
|
+
|
302
|
+
# Initialize and start listening for configuration updates
|
303
|
+
updater.init()
|
304
|
+
|
305
|
+
# Run the update loop
|
306
|
+
updater.run() # Blocks, continuously checking for updates
|
307
|
+
```
|
308
|
+
|
309
|
+
### `notify_trigger.py` - PostgreSQL LISTEN/NOTIFY Support
|
310
|
+
|
311
|
+
Create database triggers that send notifications on table changes:
|
312
|
+
|
313
|
+
```python
|
314
|
+
from ry_pg_utils.notify_trigger import (
|
315
|
+
create_notify_trigger,
|
316
|
+
drop_notify_trigger,
|
317
|
+
subscribe_to_notifications,
|
318
|
+
NotificationListener,
|
319
|
+
)
|
320
|
+
from ry_pg_utils.connect import get_engine
|
321
|
+
|
322
|
+
engine = get_engine("myapp_db")
|
323
|
+
|
324
|
+
# Create a trigger that notifies on INSERT/UPDATE/DELETE
|
325
|
+
create_notify_trigger(
|
326
|
+
engine=engine,
|
327
|
+
table_name="users",
|
328
|
+
channel_name="user_changes",
|
329
|
+
events=["INSERT", "UPDATE", "DELETE"],
|
330
|
+
columns=["id", "username", "email"] # Optional: only include specific columns
|
331
|
+
)
|
332
|
+
|
333
|
+
# Subscribe to notifications
|
334
|
+
def handle_notification(notification):
|
335
|
+
print(f"Table: {notification['table']}")
|
336
|
+
print(f"Action: {notification['action']}")
|
337
|
+
print(f"Data: {notification['data']}")
|
338
|
+
|
339
|
+
with subscribe_to_notifications(
|
340
|
+
engine=engine,
|
341
|
+
channel_name="user_changes",
|
342
|
+
callback=handle_notification,
|
343
|
+
timeout=60.0
|
344
|
+
) as notifications:
|
345
|
+
# Notifications are handled in background thread
|
346
|
+
time.sleep(10)
|
347
|
+
|
348
|
+
# Or use the NotificationListener class for long-running listeners
|
349
|
+
listener = NotificationListener(db_name="myapp_db")
|
350
|
+
listener.create_listener(
|
351
|
+
table_name="users",
|
352
|
+
channel_name="user_changes",
|
353
|
+
events=["INSERT", "UPDATE"]
|
354
|
+
)
|
355
|
+
listener.add_callback("user_changes", handle_notification)
|
356
|
+
listener.start()
|
357
|
+
|
358
|
+
# ... your application code ...
|
359
|
+
|
360
|
+
listener.stop()
|
361
|
+
```
|
362
|
+
|
363
|
+
### `ipc/channels.py` - Redis Communication Channels
|
364
|
+
|
365
|
+
Pre-defined Redis channels for database configuration updates (requires ry_redis_bus):
|
366
|
+
|
367
|
+
```python
|
368
|
+
from ry_pg_utils.ipc.channels import (
|
369
|
+
DATABASE_CHANNEL, # DatabaseConfigPb messages
|
370
|
+
DATABASE_CONFIG_CHANNEL, # DatabaseSettingsPb messages
|
371
|
+
DATABASE_NOTIFY_CHANNEL, # DatabaseNotificationPb messages
|
280
372
|
)
|
281
373
|
|
282
|
-
#
|
283
|
-
updater.run()
|
374
|
+
# These channels are used by DbUpdater for dynamic database configuration
|
284
375
|
```
|
285
376
|
|
286
377
|
## Advanced Usage
|
287
378
|
|
288
379
|
### Multi-Backend Support
|
289
380
|
|
290
|
-
When `add_backend_to_all` is enabled, all tables automatically get a `backend_id` column:
|
381
|
+
When `add_backend_to_all` is enabled (default: True), all tables automatically get a `backend_id` column:
|
291
382
|
|
292
383
|
```python
|
293
384
|
from ry_pg_utils.connect import set_backend_id, ManagedSession
|
385
|
+
from sqlalchemy import text
|
294
386
|
|
295
387
|
# Set backend ID for current thread
|
296
388
|
set_backend_id("backend_1")
|
297
389
|
|
298
390
|
# All subsequent operations will include this backend_id
|
299
|
-
|
300
|
-
|
301
|
-
|
391
|
+
# ManagedSession automatically sets the backend_id if provided
|
392
|
+
with ManagedSession(db="myapp_db", backend_id="backend_1") as session:
|
393
|
+
if session:
|
394
|
+
# New/dirty records automatically get backend_id injected before flush
|
395
|
+
result = session.execute(text("SELECT * FROM my_table"))
|
302
396
|
```
|
303
397
|
|
304
398
|
### Custom Table Names
|
305
399
|
|
306
|
-
When `add_backend_to_tables` is enabled, table names are automatically suffixed:
|
400
|
+
When `add_backend_to_tables` is enabled (default: True), table names are automatically suffixed:
|
307
401
|
|
308
402
|
```python
|
309
403
|
from ry_pg_utils.connect import get_table_name
|
404
|
+
from ry_pg_utils.config import pg_config
|
310
405
|
|
311
406
|
# Returns "events_my_backend" if add_backend_to_tables=True
|
312
|
-
table_name = get_table_name("events", backend_id="my_backend")
|
407
|
+
table_name = get_table_name("events", backend_id="my_backend", verbose=True)
|
408
|
+
|
409
|
+
# Configure globally
|
410
|
+
pg_config.add_backend_to_tables = False # Disable backend suffix
|
313
411
|
```
|
314
412
|
|
315
413
|
### ORM Base Class
|
@@ -327,27 +425,53 @@ class User(Base):
|
|
327
425
|
name = Column(String(100))
|
328
426
|
email = Column(String(200))
|
329
427
|
|
330
|
-
# If add_backend_to_all=True, backend_id column is automatically added
|
428
|
+
# If add_backend_to_all=True (default), backend_id column is automatically added
|
429
|
+
# The backend_id is a String(256) column, nullable=False
|
430
|
+
```
|
431
|
+
|
432
|
+
### Auto-Importing Models
|
433
|
+
|
434
|
+
Use the `models_module` parameter to automatically import all models before table creation:
|
435
|
+
|
436
|
+
```python
|
437
|
+
from ry_pg_utils.connect import init_database
|
438
|
+
|
439
|
+
# This will walk through 'myapp.models' and import all submodules
|
440
|
+
# ensuring all model classes are registered with Base.metadata
|
441
|
+
init_database(
|
442
|
+
db_name="myapp_db",
|
443
|
+
db_host="localhost",
|
444
|
+
db_port=5432,
|
445
|
+
db_user="postgres",
|
446
|
+
db_password="secret",
|
447
|
+
models_module="myapp.models" # Dot-separated module path
|
448
|
+
)
|
331
449
|
```
|
332
450
|
|
333
451
|
## Error Handling
|
334
452
|
|
335
|
-
The library includes robust error handling:
|
453
|
+
The library includes robust error handling with automatic retries:
|
336
454
|
|
337
455
|
```python
|
338
456
|
from ry_pg_utils.connect import ManagedSession
|
457
|
+
from sqlalchemy import text
|
339
458
|
|
340
|
-
with ManagedSession(db="
|
459
|
+
with ManagedSession(db="myapp_db") as session:
|
341
460
|
if session is None:
|
342
|
-
# Connection failed, handle gracefully
|
461
|
+
# Connection failed after retries, handle gracefully
|
343
462
|
print("Failed to establish database connection")
|
344
463
|
return
|
345
464
|
|
346
465
|
try:
|
347
|
-
session.execute("SELECT * FROM my_table")
|
466
|
+
session.execute(text("SELECT * FROM my_table"))
|
348
467
|
except Exception as e:
|
349
|
-
# Session will automatically rollback
|
468
|
+
# Session will automatically rollback on exception
|
350
469
|
print(f"Query failed: {e}")
|
470
|
+
|
471
|
+
# Retries are built-in:
|
472
|
+
# - Session operations retry 3 times on OperationalError
|
473
|
+
# - Exponential backoff: min 4s, max 10s
|
474
|
+
# - Connection health checks via pool_pre_ping
|
351
475
|
```
|
352
476
|
|
353
477
|
## Type Safety
|
@@ -378,12 +502,17 @@ pip install -r packages/requirements-dev.txt
|
|
378
502
|
|
379
503
|
### Running Tests
|
380
504
|
|
505
|
+
Tests require a running PostgreSQL instance. Configure test database connection via environment variables or `.env` file.
|
506
|
+
|
381
507
|
```bash
|
382
508
|
# Activate virtual environment
|
383
509
|
source venv-dev/bin/activate
|
384
510
|
|
385
|
-
# Run tests
|
386
|
-
|
511
|
+
# Run all tests
|
512
|
+
make test
|
513
|
+
|
514
|
+
# Run specific test module
|
515
|
+
make test TESTMODULE=connect_test
|
387
516
|
```
|
388
517
|
|
389
518
|
### Code Quality
|
@@ -392,16 +521,12 @@ The project uses several tools for code quality:
|
|
392
521
|
|
393
522
|
```bash
|
394
523
|
# Format code
|
395
|
-
|
524
|
+
make format
|
396
525
|
|
397
|
-
#
|
398
|
-
|
526
|
+
# Run linting
|
527
|
+
make lint_full
|
399
528
|
|
400
|
-
#
|
401
|
-
pylint ry_pg_utils/
|
402
|
-
|
403
|
-
# Import sorting
|
404
|
-
isort ry_pg_utils/
|
529
|
+
# Type checking is included in lint_full (uses mypy)
|
405
530
|
```
|
406
531
|
|
407
532
|
## Examples
|
@@ -412,8 +537,8 @@ isort ry_pg_utils/
|
|
412
537
|
import argparse
|
413
538
|
from ry_pg_utils.parse_args import add_postrgres_db_args
|
414
539
|
from ry_pg_utils.connect import init_database, ManagedSession
|
415
|
-
from ry_pg_utils.postgres_info import PostgresInfo
|
416
540
|
from ry_pg_utils.dynamic_table import DynamicTableDb
|
541
|
+
from sqlalchemy import text
|
417
542
|
|
418
543
|
def parse_args():
|
419
544
|
parser = argparse.ArgumentParser(description="My Database App")
|
@@ -424,26 +549,78 @@ def main():
|
|
424
549
|
args = parse_args()
|
425
550
|
|
426
551
|
# Initialize database
|
427
|
-
|
552
|
+
init_database(
|
428
553
|
db_name=args.postgres_db,
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
554
|
+
db_host=args.postgres_host,
|
555
|
+
db_port=args.postgres_port,
|
556
|
+
db_user=args.postgres_user,
|
557
|
+
db_password=args.postgres_password
|
433
558
|
)
|
434
559
|
|
435
|
-
init_database(db_info, db_name="myapp")
|
436
|
-
|
437
560
|
# Use the database
|
438
|
-
with ManagedSession(db=
|
561
|
+
with ManagedSession(db=args.postgres_db) as session:
|
439
562
|
if session:
|
440
|
-
result = session.execute("SELECT version()")
|
563
|
+
result = session.execute(text("SELECT version()"))
|
441
564
|
print(f"PostgreSQL version: {result.fetchone()[0]}")
|
442
565
|
|
443
566
|
if __name__ == "__main__":
|
444
567
|
main()
|
445
568
|
```
|
446
569
|
|
570
|
+
### Real-time Notification Example
|
571
|
+
|
572
|
+
```python
|
573
|
+
from ry_pg_utils.connect import init_database, get_engine, Base
|
574
|
+
from ry_pg_utils.notify_trigger import create_notify_trigger, NotificationListener
|
575
|
+
from sqlalchemy import Column, Integer, String
|
576
|
+
import time
|
577
|
+
|
578
|
+
# Define a model
|
579
|
+
class Product(Base):
|
580
|
+
__tablename__ = 'products'
|
581
|
+
|
582
|
+
id = Column(Integer, primary_key=True)
|
583
|
+
name = Column(String(100))
|
584
|
+
price = Column(Integer)
|
585
|
+
|
586
|
+
# Initialize database
|
587
|
+
init_database(
|
588
|
+
db_name="inventory_db",
|
589
|
+
db_host="localhost",
|
590
|
+
db_port=5432,
|
591
|
+
db_user="postgres",
|
592
|
+
db_password="secret"
|
593
|
+
)
|
594
|
+
|
595
|
+
# Create notification trigger
|
596
|
+
engine = get_engine("inventory_db")
|
597
|
+
create_notify_trigger(
|
598
|
+
engine=engine,
|
599
|
+
table_name="products",
|
600
|
+
channel_name="product_updates",
|
601
|
+
events=["INSERT", "UPDATE", "DELETE"],
|
602
|
+
columns=["id", "name", "price"]
|
603
|
+
)
|
604
|
+
|
605
|
+
# Set up listener
|
606
|
+
listener = NotificationListener(db_name="inventory_db")
|
607
|
+
|
608
|
+
def on_product_change(notification):
|
609
|
+
action = notification['action']
|
610
|
+
data = notification['data']
|
611
|
+
print(f"Product {action}: {data}")
|
612
|
+
|
613
|
+
listener.add_callback("product_updates", on_product_change)
|
614
|
+
listener.start()
|
615
|
+
|
616
|
+
# Your application runs...
|
617
|
+
try:
|
618
|
+
while True:
|
619
|
+
time.sleep(1)
|
620
|
+
except KeyboardInterrupt:
|
621
|
+
listener.stop()
|
622
|
+
```
|
623
|
+
|
447
624
|
## License
|
448
625
|
|
449
626
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -464,10 +641,19 @@ Ross Yeager - ryeager12@email.com
|
|
464
641
|
|
465
642
|
## Changelog
|
466
643
|
|
644
|
+
### Version 1.0.2 (Current)
|
645
|
+
- PostgreSQL LISTEN/NOTIFY support with triggers and notifications
|
646
|
+
- NotificationListener class for background notification handling
|
647
|
+
- Automatic connection health checks with pool_pre_ping
|
648
|
+
- Auto-importing models from specified module paths
|
649
|
+
- Enhanced retry logic with tenacity
|
650
|
+
- Improved error handling and connection recovery
|
651
|
+
|
467
652
|
### Version 1.0.0
|
468
653
|
- Initial release
|
469
|
-
- Database connection management
|
470
|
-
- Dynamic table creation
|
471
|
-
- Multi-backend support
|
472
|
-
- Configuration system
|
654
|
+
- Database connection management with pooling
|
655
|
+
- Dynamic table creation from Protocol Buffers
|
656
|
+
- Multi-backend support with automatic ID tagging
|
657
|
+
- Configuration system with environment variables
|
473
658
|
- Protocol Buffer integration
|
659
|
+
- Redis-based database configuration updates
|