ry-pg-utils 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
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/config.py +11 -0
- ry_pg_utils/ipc/channels.py +0 -2
- {ry_pg_utils-1.0.2.dist-info → ry_pg_utils-1.0.4.dist-info}/METADATA +268 -78
- {ry_pg_utils-1.0.2.dist-info → ry_pg_utils-1.0.4.dist-info}/RECORD +7 -7
- {ry_pg_utils-1.0.2.dist-info → ry_pg_utils-1.0.4.dist-info}/WHEEL +0 -0
- {ry_pg_utils-1.0.2.dist-info → ry_pg_utils-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {ry_pg_utils-1.0.2.dist-info → ry_pg_utils-1.0.4.dist-info}/top_level.txt +0 -0
ry_pg_utils/config.py
CHANGED
@@ -18,6 +18,10 @@ class Config:
|
|
18
18
|
add_backend_to_all: bool
|
19
19
|
add_backend_to_tables: bool
|
20
20
|
raise_on_use_before_init: bool
|
21
|
+
ssh_host: str | None
|
22
|
+
ssh_port: int | None
|
23
|
+
ssh_user: str | None
|
24
|
+
ssh_key_path: str | None
|
21
25
|
|
22
26
|
|
23
27
|
dotenv.load_dotenv()
|
@@ -26,6 +30,9 @@ dotenv.load_dotenv()
|
|
26
30
|
_postgres_port_str = os.getenv("POSTGRES_PORT")
|
27
31
|
_postgres_port = int(_postgres_port_str) if _postgres_port_str is not None else None
|
28
32
|
|
33
|
+
_ssh_port_str = os.getenv("SSH_PORT")
|
34
|
+
_ssh_port = int(_ssh_port_str) if _ssh_port_str is not None else None
|
35
|
+
|
29
36
|
pg_config = Config(
|
30
37
|
postgres_host=os.getenv("POSTGRES_HOST"),
|
31
38
|
postgres_port=_postgres_port,
|
@@ -41,4 +48,8 @@ pg_config = Config(
|
|
41
48
|
add_backend_to_all=True,
|
42
49
|
add_backend_to_tables=True,
|
43
50
|
raise_on_use_before_init=True,
|
51
|
+
ssh_host=os.getenv("SSH_HOST"),
|
52
|
+
ssh_port=_ssh_port,
|
53
|
+
ssh_user=os.getenv("SSH_USER"),
|
54
|
+
ssh_key_path=os.getenv("SSH_KEY_PATH"),
|
44
55
|
)
|
ry_pg_utils/ipc/channels.py
CHANGED
@@ -3,8 +3,6 @@ from ry_redis_bus.channels import Channel
|
|
3
3
|
from ry_pg_utils.pb_types.database_pb2 import DatabaseConfigPb # pylint: disable=no-name-in-module
|
4
4
|
from ry_pg_utils.pb_types.database_pb2 import ( # pylint: disable=no-name-in-module
|
5
5
|
DatabaseNotificationPb,
|
6
|
-
)
|
7
|
-
from ry_pg_utils.pb_types.database_pb2 import ( # pylint: disable=no-name-in-module
|
8
6
|
DatabaseSettingsPb,
|
9
7
|
)
|
10
8
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ry-pg-utils
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
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,29 @@ 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:**
|
205
|
+
|
195
206
|
- Thread-local backend ID tracking
|
196
|
-
- Connection pooling with configurable parameters
|
197
|
-
- Automatic connection recovery
|
207
|
+
- Connection pooling with configurable parameters (pool_size, max_overflow, pool_recycle)
|
208
|
+
- Automatic connection recovery with retry logic (using tenacity)
|
198
209
|
- Session scoping for thread safety
|
210
|
+
- Automatic backend_id injection before flush operations
|
211
|
+
- Pre-ping health checks to validate connections
|
212
|
+
- Support for auto-importing model modules
|
199
213
|
|
200
214
|
### `dynamic_table.py` - Dynamic Table Creation
|
201
215
|
|
@@ -225,6 +239,7 @@ exists = db.inst_is_in_db(
|
|
225
239
|
```
|
226
240
|
|
227
241
|
**Supported Protocol Buffer Types:**
|
242
|
+
|
228
243
|
- `int32`, `int64`, `uint32`, `uint64` → PostgreSQL `Integer`
|
229
244
|
- `float`, `double` → PostgreSQL `Float`
|
230
245
|
- `bool` → PostgreSQL `Boolean`
|
@@ -234,7 +249,7 @@ exists = db.inst_is_in_db(
|
|
234
249
|
|
235
250
|
### `postgres_info.py` - Connection Information
|
236
251
|
|
237
|
-
|
252
|
+
Class for PostgreSQL connection parameters:
|
238
253
|
|
239
254
|
```python
|
240
255
|
from ry_pg_utils.postgres_info import PostgresInfo
|
@@ -247,8 +262,12 @@ db_info = PostgresInfo(
|
|
247
262
|
password="secret"
|
248
263
|
)
|
249
264
|
|
250
|
-
#
|
251
|
-
|
265
|
+
# Check if valid
|
266
|
+
if not db_info.is_null():
|
267
|
+
print(db_info) # Prints info with password masked
|
268
|
+
|
269
|
+
# Create null instance
|
270
|
+
null_info = PostgresInfo.null()
|
252
271
|
```
|
253
272
|
|
254
273
|
### `parse_args.py` - Argument Parsing
|
@@ -268,48 +287,129 @@ args = parser.parse_args()
|
|
268
287
|
|
269
288
|
### `updater.py` - Database Configuration Updater
|
270
289
|
|
271
|
-
Dynamically update database connections based on configuration messages:
|
290
|
+
Dynamically update database connections based on configuration messages via Redis:
|
272
291
|
|
273
292
|
```python
|
274
|
-
from ry_pg_utils.updater import
|
293
|
+
from ry_pg_utils.updater import DbUpdater
|
294
|
+
from ry_redis_bus.helpers import RedisInfo
|
295
|
+
from ryutils.verbose import Verbose
|
275
296
|
|
276
|
-
updater =
|
297
|
+
updater = DbUpdater(
|
277
298
|
redis_info=redis_info,
|
278
|
-
|
279
|
-
backend_id="my_backend"
|
299
|
+
args=args, # argparse.Namespace with postgres config
|
300
|
+
backend_id="my_backend",
|
301
|
+
verbose=Verbose(True)
|
302
|
+
)
|
303
|
+
|
304
|
+
# Initialize and start listening for configuration updates
|
305
|
+
updater.init()
|
306
|
+
|
307
|
+
# Run the update loop
|
308
|
+
updater.run() # Blocks, continuously checking for updates
|
309
|
+
```
|
310
|
+
|
311
|
+
### `notify_trigger.py` - PostgreSQL LISTEN/NOTIFY Support
|
312
|
+
|
313
|
+
Create database triggers that send notifications on table changes:
|
314
|
+
|
315
|
+
```python
|
316
|
+
from ry_pg_utils.notify_trigger import (
|
317
|
+
create_notify_trigger,
|
318
|
+
drop_notify_trigger,
|
319
|
+
subscribe_to_notifications,
|
320
|
+
NotificationListener,
|
280
321
|
)
|
322
|
+
from ry_pg_utils.connect import get_engine
|
323
|
+
|
324
|
+
engine = get_engine("myapp_db")
|
325
|
+
|
326
|
+
# Create a trigger that notifies on INSERT/UPDATE/DELETE
|
327
|
+
create_notify_trigger(
|
328
|
+
engine=engine,
|
329
|
+
table_name="users",
|
330
|
+
channel_name="user_changes",
|
331
|
+
events=["INSERT", "UPDATE", "DELETE"],
|
332
|
+
columns=["id", "username", "email"] # Optional: only include specific columns
|
333
|
+
)
|
334
|
+
|
335
|
+
# Subscribe to notifications
|
336
|
+
def handle_notification(notification):
|
337
|
+
print(f"Table: {notification['table']}")
|
338
|
+
print(f"Action: {notification['action']}")
|
339
|
+
print(f"Data: {notification['data']}")
|
340
|
+
|
341
|
+
with subscribe_to_notifications(
|
342
|
+
engine=engine,
|
343
|
+
channel_name="user_changes",
|
344
|
+
callback=handle_notification,
|
345
|
+
timeout=60.0
|
346
|
+
) as notifications:
|
347
|
+
# Notifications are handled in background thread
|
348
|
+
time.sleep(10)
|
349
|
+
|
350
|
+
# Or use the NotificationListener class for long-running listeners
|
351
|
+
listener = NotificationListener(db_name="myapp_db")
|
352
|
+
listener.create_listener(
|
353
|
+
table_name="users",
|
354
|
+
channel_name="user_changes",
|
355
|
+
events=["INSERT", "UPDATE"]
|
356
|
+
)
|
357
|
+
listener.add_callback("user_changes", handle_notification)
|
358
|
+
listener.start()
|
359
|
+
|
360
|
+
# ... your application code ...
|
281
361
|
|
282
|
-
|
283
|
-
|
362
|
+
listener.stop()
|
363
|
+
```
|
364
|
+
|
365
|
+
### `ipc/channels.py` - Redis Communication Channels
|
366
|
+
|
367
|
+
Pre-defined Redis channels for database configuration updates (requires ry_redis_bus):
|
368
|
+
|
369
|
+
```python
|
370
|
+
from ry_pg_utils.ipc.channels import (
|
371
|
+
DATABASE_CHANNEL, # DatabaseConfigPb messages
|
372
|
+
DATABASE_CONFIG_CHANNEL, # DatabaseSettingsPb messages
|
373
|
+
DATABASE_NOTIFY_CHANNEL, # DatabaseNotificationPb messages
|
374
|
+
)
|
375
|
+
|
376
|
+
# These channels are used by DbUpdater for dynamic database configuration
|
284
377
|
```
|
285
378
|
|
286
379
|
## Advanced Usage
|
287
380
|
|
288
381
|
### Multi-Backend Support
|
289
382
|
|
290
|
-
When `add_backend_to_all` is enabled, all tables automatically get a `backend_id` column:
|
383
|
+
When `add_backend_to_all` is enabled (default: True), all tables automatically get a `backend_id` column:
|
291
384
|
|
292
385
|
```python
|
293
386
|
from ry_pg_utils.connect import set_backend_id, ManagedSession
|
387
|
+
from sqlalchemy import text
|
294
388
|
|
295
389
|
# Set backend ID for current thread
|
296
390
|
set_backend_id("backend_1")
|
297
391
|
|
298
392
|
# All subsequent operations will include this backend_id
|
299
|
-
|
300
|
-
|
301
|
-
|
393
|
+
# ManagedSession automatically sets the backend_id if provided
|
394
|
+
with ManagedSession(db="myapp_db", backend_id="backend_1") as session:
|
395
|
+
if session:
|
396
|
+
# New/dirty records automatically get backend_id injected before flush
|
397
|
+
result = session.execute(text("SELECT * FROM my_table"))
|
302
398
|
```
|
303
399
|
|
304
400
|
### Custom Table Names
|
305
401
|
|
306
|
-
When `add_backend_to_tables` is enabled, table names are automatically suffixed:
|
402
|
+
When `add_backend_to_tables` is enabled (default: True), table names are automatically suffixed:
|
307
403
|
|
308
404
|
```python
|
309
405
|
from ry_pg_utils.connect import get_table_name
|
406
|
+
from ry_pg_utils.config import pg_config
|
310
407
|
|
311
408
|
# Returns "events_my_backend" if add_backend_to_tables=True
|
312
|
-
table_name = get_table_name("events", backend_id="my_backend")
|
409
|
+
table_name = get_table_name("events", backend_id="my_backend", verbose=True)
|
410
|
+
|
411
|
+
# Configure globally
|
412
|
+
pg_config.add_backend_to_tables = False # Disable backend suffix
|
313
413
|
```
|
314
414
|
|
315
415
|
### ORM Base Class
|
@@ -327,27 +427,53 @@ class User(Base):
|
|
327
427
|
name = Column(String(100))
|
328
428
|
email = Column(String(200))
|
329
429
|
|
330
|
-
# If add_backend_to_all=True, backend_id column is automatically added
|
430
|
+
# If add_backend_to_all=True (default), backend_id column is automatically added
|
431
|
+
# The backend_id is a String(256) column, nullable=False
|
432
|
+
```
|
433
|
+
|
434
|
+
### Auto-Importing Models
|
435
|
+
|
436
|
+
Use the `models_module` parameter to automatically import all models before table creation:
|
437
|
+
|
438
|
+
```python
|
439
|
+
from ry_pg_utils.connect import init_database
|
440
|
+
|
441
|
+
# This will walk through 'myapp.models' and import all submodules
|
442
|
+
# ensuring all model classes are registered with Base.metadata
|
443
|
+
init_database(
|
444
|
+
db_name="myapp_db",
|
445
|
+
db_host="localhost",
|
446
|
+
db_port=5432,
|
447
|
+
db_user="postgres",
|
448
|
+
db_password="secret",
|
449
|
+
models_module="myapp.models" # Dot-separated module path
|
450
|
+
)
|
331
451
|
```
|
332
452
|
|
333
453
|
## Error Handling
|
334
454
|
|
335
|
-
The library includes robust error handling:
|
455
|
+
The library includes robust error handling with automatic retries:
|
336
456
|
|
337
457
|
```python
|
338
458
|
from ry_pg_utils.connect import ManagedSession
|
459
|
+
from sqlalchemy import text
|
339
460
|
|
340
|
-
with ManagedSession(db="
|
461
|
+
with ManagedSession(db="myapp_db") as session:
|
341
462
|
if session is None:
|
342
|
-
# Connection failed, handle gracefully
|
463
|
+
# Connection failed after retries, handle gracefully
|
343
464
|
print("Failed to establish database connection")
|
344
465
|
return
|
345
466
|
|
346
467
|
try:
|
347
|
-
session.execute("SELECT * FROM my_table")
|
468
|
+
session.execute(text("SELECT * FROM my_table"))
|
348
469
|
except Exception as e:
|
349
|
-
# Session will automatically rollback
|
470
|
+
# Session will automatically rollback on exception
|
350
471
|
print(f"Query failed: {e}")
|
472
|
+
|
473
|
+
# Retries are built-in:
|
474
|
+
# - Session operations retry 3 times on OperationalError
|
475
|
+
# - Exponential backoff: min 4s, max 10s
|
476
|
+
# - Connection health checks via pool_pre_ping
|
351
477
|
```
|
352
478
|
|
353
479
|
## Type Safety
|
@@ -378,12 +504,17 @@ pip install -r packages/requirements-dev.txt
|
|
378
504
|
|
379
505
|
### Running Tests
|
380
506
|
|
507
|
+
Tests require a running PostgreSQL instance. Configure test database connection via environment variables or `.env` file.
|
508
|
+
|
381
509
|
```bash
|
382
510
|
# Activate virtual environment
|
383
511
|
source venv-dev/bin/activate
|
384
512
|
|
385
|
-
# Run tests
|
386
|
-
|
513
|
+
# Run all tests
|
514
|
+
make test
|
515
|
+
|
516
|
+
# Run specific test module
|
517
|
+
make test TESTMODULE=connect_test
|
387
518
|
```
|
388
519
|
|
389
520
|
### Code Quality
|
@@ -392,16 +523,12 @@ The project uses several tools for code quality:
|
|
392
523
|
|
393
524
|
```bash
|
394
525
|
# Format code
|
395
|
-
|
396
|
-
|
397
|
-
# Type checking
|
398
|
-
mypy ry_pg_utils/
|
526
|
+
make format
|
399
527
|
|
400
|
-
#
|
401
|
-
|
528
|
+
# Run linting
|
529
|
+
make lint_full
|
402
530
|
|
403
|
-
#
|
404
|
-
isort ry_pg_utils/
|
531
|
+
# Type checking is included in lint_full (uses mypy)
|
405
532
|
```
|
406
533
|
|
407
534
|
## Examples
|
@@ -412,8 +539,8 @@ isort ry_pg_utils/
|
|
412
539
|
import argparse
|
413
540
|
from ry_pg_utils.parse_args import add_postrgres_db_args
|
414
541
|
from ry_pg_utils.connect import init_database, ManagedSession
|
415
|
-
from ry_pg_utils.postgres_info import PostgresInfo
|
416
542
|
from ry_pg_utils.dynamic_table import DynamicTableDb
|
543
|
+
from sqlalchemy import text
|
417
544
|
|
418
545
|
def parse_args():
|
419
546
|
parser = argparse.ArgumentParser(description="My Database App")
|
@@ -424,26 +551,78 @@ def main():
|
|
424
551
|
args = parse_args()
|
425
552
|
|
426
553
|
# Initialize database
|
427
|
-
|
554
|
+
init_database(
|
428
555
|
db_name=args.postgres_db,
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
556
|
+
db_host=args.postgres_host,
|
557
|
+
db_port=args.postgres_port,
|
558
|
+
db_user=args.postgres_user,
|
559
|
+
db_password=args.postgres_password
|
433
560
|
)
|
434
561
|
|
435
|
-
init_database(db_info, db_name="myapp")
|
436
|
-
|
437
562
|
# Use the database
|
438
|
-
with ManagedSession(db=
|
563
|
+
with ManagedSession(db=args.postgres_db) as session:
|
439
564
|
if session:
|
440
|
-
result = session.execute("SELECT version()")
|
565
|
+
result = session.execute(text("SELECT version()"))
|
441
566
|
print(f"PostgreSQL version: {result.fetchone()[0]}")
|
442
567
|
|
443
568
|
if __name__ == "__main__":
|
444
569
|
main()
|
445
570
|
```
|
446
571
|
|
572
|
+
### Real-time Notification Example
|
573
|
+
|
574
|
+
```python
|
575
|
+
from ry_pg_utils.connect import init_database, get_engine, Base
|
576
|
+
from ry_pg_utils.notify_trigger import create_notify_trigger, NotificationListener
|
577
|
+
from sqlalchemy import Column, Integer, String
|
578
|
+
import time
|
579
|
+
|
580
|
+
# Define a model
|
581
|
+
class Product(Base):
|
582
|
+
__tablename__ = 'products'
|
583
|
+
|
584
|
+
id = Column(Integer, primary_key=True)
|
585
|
+
name = Column(String(100))
|
586
|
+
price = Column(Integer)
|
587
|
+
|
588
|
+
# Initialize database
|
589
|
+
init_database(
|
590
|
+
db_name="inventory_db",
|
591
|
+
db_host="localhost",
|
592
|
+
db_port=5432,
|
593
|
+
db_user="postgres",
|
594
|
+
db_password="secret"
|
595
|
+
)
|
596
|
+
|
597
|
+
# Create notification trigger
|
598
|
+
engine = get_engine("inventory_db")
|
599
|
+
create_notify_trigger(
|
600
|
+
engine=engine,
|
601
|
+
table_name="products",
|
602
|
+
channel_name="product_updates",
|
603
|
+
events=["INSERT", "UPDATE", "DELETE"],
|
604
|
+
columns=["id", "name", "price"]
|
605
|
+
)
|
606
|
+
|
607
|
+
# Set up listener
|
608
|
+
listener = NotificationListener(db_name="inventory_db")
|
609
|
+
|
610
|
+
def on_product_change(notification):
|
611
|
+
action = notification['action']
|
612
|
+
data = notification['data']
|
613
|
+
print(f"Product {action}: {data}")
|
614
|
+
|
615
|
+
listener.add_callback("product_updates", on_product_change)
|
616
|
+
listener.start()
|
617
|
+
|
618
|
+
# Your application runs...
|
619
|
+
try:
|
620
|
+
while True:
|
621
|
+
time.sleep(1)
|
622
|
+
except KeyboardInterrupt:
|
623
|
+
listener.stop()
|
624
|
+
```
|
625
|
+
|
447
626
|
## License
|
448
627
|
|
449
628
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -460,14 +639,25 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
460
639
|
|
461
640
|
## Author
|
462
641
|
|
463
|
-
Ross Yeager - ryeager12@email.com
|
642
|
+
Ross Yeager - `ryeager12@email.com`
|
464
643
|
|
465
644
|
## Changelog
|
466
645
|
|
646
|
+
### Version 1.0.2 (Current)
|
647
|
+
|
648
|
+
- PostgreSQL LISTEN/NOTIFY support with triggers and notifications
|
649
|
+
- NotificationListener class for background notification handling
|
650
|
+
- Automatic connection health checks with pool_pre_ping
|
651
|
+
- Auto-importing models from specified module paths
|
652
|
+
- Enhanced retry logic with tenacity
|
653
|
+
- Improved error handling and connection recovery
|
654
|
+
|
467
655
|
### Version 1.0.0
|
656
|
+
|
468
657
|
- Initial release
|
469
|
-
- Database connection management
|
470
|
-
- Dynamic table creation
|
471
|
-
- Multi-backend support
|
472
|
-
- Configuration system
|
658
|
+
- Database connection management with pooling
|
659
|
+
- Dynamic table creation from Protocol Buffers
|
660
|
+
- Multi-backend support with automatic ID tagging
|
661
|
+
- Configuration system with environment variables
|
473
662
|
- Protocol Buffer integration
|
663
|
+
- Redis-based database configuration updates
|
@@ -1,5 +1,5 @@
|
|
1
1
|
ry_pg_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
ry_pg_utils/config.py,sha256=
|
2
|
+
ry_pg_utils/config.py,sha256=7W-JLNxZDoursPArtoKyOiEqUABGyO1ZEkTdlVBzdVM,1522
|
3
3
|
ry_pg_utils/connect.py,sha256=jF-Sj9oySPDfTlOh4_C6c03t4KW5Sef_MLHx_sh52a4,10108
|
4
4
|
ry_pg_utils/dynamic_table.py,sha256=VBkfbATRk_pW3EtAFHvMkGG0fLZMebw_LdWkFaDDugw,6866
|
5
5
|
ry_pg_utils/notify_trigger.py,sha256=_dsuPkoqjnSQqWrsdZK3FqI6zG5UuygVXHwtERwN99Y,12069
|
@@ -8,13 +8,13 @@ ry_pg_utils/postgres_info.py,sha256=mj9er830jvrJXUFuFKx1EmZMjuEpMDcd901kgYb_Cck,
|
|
8
8
|
ry_pg_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
ry_pg_utils/updater.py,sha256=TbUKJHQarzRrDjcbjbzIhy1sO61o-YhEG594gWnI0LA,5831
|
10
10
|
ry_pg_utils/ipc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
ry_pg_utils/ipc/channels.py,sha256=
|
11
|
+
ry_pg_utils/ipc/channels.py,sha256=sjYuRjmqWMN26v8m6Qm72lecwOJ1dZAF-qfcDR-qjsY,525
|
12
12
|
ry_pg_utils/pb_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
ry_pg_utils/pb_types/database_pb2.py,sha256=yf__HycGrVoU8pzhYdnOhXhfAkgjeGl80ls_RsLemy4,2780
|
14
14
|
ry_pg_utils/pb_types/database_pb2.pyi,sha256=svpmlEY_99dA_u3CJqn7CO5D7CH2pAGTOqlhVMZLXC4,5689
|
15
15
|
ry_pg_utils/pb_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
ry_pg_utils-1.0.
|
17
|
-
ry_pg_utils-1.0.
|
18
|
-
ry_pg_utils-1.0.
|
19
|
-
ry_pg_utils-1.0.
|
20
|
-
ry_pg_utils-1.0.
|
16
|
+
ry_pg_utils-1.0.4.dist-info/licenses/LICENSE,sha256=PYnig94SABlde939TWrqvOqQkkfjuHttf8KpWKlPFlA,1068
|
17
|
+
ry_pg_utils-1.0.4.dist-info/METADATA,sha256=_ogikIhPdaeRF5KQRAg4Ie9Z-I7TPuwxCkm0t-GAVB0,18043
|
18
|
+
ry_pg_utils-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
19
|
+
ry_pg_utils-1.0.4.dist-info/top_level.txt,sha256=OruzbmsQHYyPnAw8RichQ5GK1Sxj-MQwWfJ93PEHXIM,12
|
20
|
+
ry_pg_utils-1.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|