ry-pg-utils 1.0.2__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 (29) hide show
  1. ry_pg_utils-1.0.2/LICENSE +21 -0
  2. ry_pg_utils-1.0.2/MANIFEST.in +2 -0
  3. ry_pg_utils-1.0.2/PKG-INFO +473 -0
  4. ry_pg_utils-1.0.2/README.md +442 -0
  5. ry_pg_utils-1.0.2/VERSION +1 -0
  6. ry_pg_utils-1.0.2/packages/base_requirements.in +14 -0
  7. ry_pg_utils-1.0.2/pyproject.toml +17 -0
  8. ry_pg_utils-1.0.2/setup.cfg +4 -0
  9. ry_pg_utils-1.0.2/setup.py +35 -0
  10. ry_pg_utils-1.0.2/src/ry_pg_utils/__init__.py +0 -0
  11. ry_pg_utils-1.0.2/src/ry_pg_utils/config.py +44 -0
  12. ry_pg_utils-1.0.2/src/ry_pg_utils/connect.py +288 -0
  13. ry_pg_utils-1.0.2/src/ry_pg_utils/dynamic_table.py +199 -0
  14. ry_pg_utils-1.0.2/src/ry_pg_utils/ipc/__init__.py +0 -0
  15. ry_pg_utils-1.0.2/src/ry_pg_utils/ipc/channels.py +14 -0
  16. ry_pg_utils-1.0.2/src/ry_pg_utils/notify_trigger.py +346 -0
  17. ry_pg_utils-1.0.2/src/ry_pg_utils/parse_args.py +15 -0
  18. ry_pg_utils-1.0.2/src/ry_pg_utils/pb_types/__init__.py +0 -0
  19. ry_pg_utils-1.0.2/src/ry_pg_utils/pb_types/database_pb2.py +38 -0
  20. ry_pg_utils-1.0.2/src/ry_pg_utils/pb_types/database_pb2.pyi +156 -0
  21. ry_pg_utils-1.0.2/src/ry_pg_utils/pb_types/py.typed +0 -0
  22. ry_pg_utils-1.0.2/src/ry_pg_utils/postgres_info.py +47 -0
  23. ry_pg_utils-1.0.2/src/ry_pg_utils/py.typed +0 -0
  24. ry_pg_utils-1.0.2/src/ry_pg_utils/updater.py +181 -0
  25. ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info/PKG-INFO +473 -0
  26. ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info/SOURCES.txt +27 -0
  27. ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info/dependency_links.txt +1 -0
  28. ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info/requires.txt +9 -0
  29. ry_pg_utils-1.0.2/src/ry_pg_utils.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ross Yeager
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include packages/base_requirements.in
2
+ include VERSION
@@ -0,0 +1,473 @@
1
+ Metadata-Version: 2.4
2
+ Name: ry-pg-utils
3
+ Version: 1.0.2
4
+ Summary: Utility functions for PostgreSQL
5
+ Author: Ross Yeager
6
+ Author-email: ryeager12@email.com
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: ry_redis_bus
15
+ Requires-Dist: ryutils
16
+ Requires-Dist: sqlalchemy
17
+ Requires-Dist: sqlalchemy_utils
18
+ Requires-Dist: psycopg2-binary
19
+ Requires-Dist: python-dotenv
20
+ Requires-Dist: tenacity
21
+ Requires-Dist: types-protobuf
22
+ Requires-Dist: types-psycopg2
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: license-file
29
+ Dynamic: requires-dist
30
+ Dynamic: summary
31
+
32
+ # ry-pg-utils
33
+
34
+ A Python utility library for PostgreSQL database operations with dynamic table creation, connection management, and Protocol Buffer integration.
35
+
36
+ ## Overview
37
+
38
+ `ry-pg-utils` provides a robust framework for working with PostgreSQL databases in Python applications. It includes utilities for:
39
+
40
+ - Database connection management with connection pooling
41
+ - Dynamic table creation from Protocol Buffer message definitions
42
+ - Thread-safe session management
43
+ - Multi-backend support with automatic backend ID tracking
44
+ - Database updater for dynamic configuration
45
+ - Argument parsing for PostgreSQL connection parameters
46
+
47
+ ## Features
48
+
49
+ - **Connection Management**: Thread-safe PostgreSQL connection pooling with automatic retry logic
50
+ - **Dynamic Tables**: Automatically create and manage database tables from Protocol Buffer message schemas
51
+ - **Multi-Backend Support**: Track data across multiple backend instances with automatic ID tagging
52
+ - **Session Management**: Context managers for safe database session handling
53
+ - **Configuration System**: Flexible configuration via environment variables and runtime settings
54
+ - **Type Safety**: Full type hints and mypy support
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install ry-pg-utils
60
+ ```
61
+
62
+ ### Dependencies
63
+
64
+ - Python 3.12+
65
+ - PostgreSQL database
66
+ - SQLAlchemy
67
+ - Protocol Buffer support
68
+
69
+ ## Configuration
70
+
71
+ `ry-pg-utils` uses a flexible configuration system that can be customized in multiple ways:
72
+
73
+ ### 1. Environment Variables
74
+
75
+ Create a `.env` file in your project root:
76
+
77
+ ```bash
78
+ POSTGRES_HOST=localhost
79
+ POSTGRES_PORT=5432
80
+ POSTGRES_DB=mydb
81
+ POSTGRES_USER=postgres
82
+ POSTGRES_PASSWORD=secret
83
+ ```
84
+
85
+ ### 2. Configuration Object
86
+
87
+ ```python
88
+ from ry_pg_utils.config import pg_config
89
+
90
+ # Access configuration
91
+ print(pg_config.postgres_host)
92
+ print(pg_config.postgres_port)
93
+
94
+ # Modify at runtime
95
+ pg_config.add_backend_to_all = False
96
+ pg_config.backend_id = "custom_backend_id"
97
+ ```
98
+
99
+ ### Configuration Options
100
+
101
+ | Option | Type | Default | Description |
102
+ |--------|------|---------|-------------|
103
+ | `postgres_host` | str | From env | PostgreSQL server hostname |
104
+ | `postgres_port` | int | From env | PostgreSQL server port |
105
+ | `postgres_db` | str | From env | Database name |
106
+ | `postgres_user` | str | From env | Database username |
107
+ | `postgres_password` | str | From env | Database password |
108
+ | `backend_id` | str | hostname_ip | Unique identifier for this backend instance |
109
+ | `add_backend_to_all` | bool | True | Add backend_id column to all tables |
110
+ | `add_backend_to_tables` | bool | True | Append backend_id to table names |
111
+ | `raise_on_use_before_init` | bool | True | Raise exception if DB used before initialization |
112
+ | `do_publish_db` | bool | False | Enable database publishing features |
113
+ | `use_local_db_only` | bool | True | Use only local database connections |
114
+
115
+ ## Quick Start
116
+
117
+ ### 1. Initialize Database Connection
118
+
119
+ ```python
120
+ from ry_pg_utils.connect import init_database, ManagedSession
121
+ from ry_pg_utils.postgres_info import PostgresInfo
122
+
123
+ # Create database connection info
124
+ db_info = PostgresInfo(
125
+ db_name="myapp_db",
126
+ host="localhost",
127
+ port=5432,
128
+ user="postgres",
129
+ password="secret"
130
+ )
131
+
132
+ # Initialize the database connection
133
+ init_database(db_info, db_name="myapp")
134
+ ```
135
+
136
+ ### 2. Use Dynamic Tables with Protocol Buffers
137
+
138
+ ```python
139
+ from ry_pg_utils.dynamic_table import DynamicTableDb
140
+ from your_app.proto import YourMessagePb
141
+
142
+ # Create a message
143
+ message = YourMessagePb()
144
+ message.field1 = "value1"
145
+ message.field2 = 42
146
+
147
+ # Log message to database (table created automatically)
148
+ DynamicTableDb.log_data_to_db(
149
+ msg=message,
150
+ db_name="myapp",
151
+ channel="my_channel"
152
+ )
153
+
154
+ # Check if data exists
155
+ exists = DynamicTableDb.is_in_db(
156
+ msg=message,
157
+ db_name="myapp",
158
+ channel="my_channel",
159
+ attr="field1",
160
+ value="value1"
161
+ )
162
+ ```
163
+
164
+ ### 3. Manual Session Management
165
+
166
+ ```python
167
+ from ry_pg_utils.connect import ManagedSession
168
+
169
+ # Use context manager for automatic session cleanup
170
+ with ManagedSession(db="myapp") as session:
171
+ result = session.execute("SELECT * FROM my_table")
172
+ for row in result:
173
+ print(row)
174
+ ```
175
+
176
+ ## Core Components
177
+
178
+ ### `connect.py` - Connection Management
179
+
180
+ The connection module provides thread-safe database connection and session management:
181
+
182
+ ```python
183
+ from ry_pg_utils.connect import (
184
+ init_database, # Initialize database connection
185
+ init_engine, # Initialize SQLAlchemy engine
186
+ ManagedSession, # Context manager for sessions
187
+ get_backend_id, # Get current backend ID
188
+ set_backend_id, # Set backend ID for thread
189
+ close_engine, # Close database connection
190
+ clear_db, # Clear all connections
191
+ )
192
+ ```
193
+
194
+ **Key Features:**
195
+ - Thread-local backend ID tracking
196
+ - Connection pooling with configurable parameters
197
+ - Automatic connection recovery on failure
198
+ - Session scoping for thread safety
199
+
200
+ ### `dynamic_table.py` - Dynamic Table Creation
201
+
202
+ Automatically create and manage database tables from Protocol Buffer definitions:
203
+
204
+ ```python
205
+ from ry_pg_utils.dynamic_table import DynamicTableDb
206
+
207
+ # Create instance
208
+ db = DynamicTableDb(db_name="myapp")
209
+
210
+ # Add message to database
211
+ db.add_message(
212
+ channel_name="events",
213
+ message_pb=my_protobuf_message,
214
+ log_print_failure=True,
215
+ verbose=True
216
+ )
217
+
218
+ # Check existence
219
+ exists = db.inst_is_in_db(
220
+ message_pb=my_protobuf_message,
221
+ channel_name="events",
222
+ attr="event_id",
223
+ value=12345
224
+ )
225
+ ```
226
+
227
+ **Supported Protocol Buffer Types:**
228
+ - `int32`, `int64`, `uint32`, `uint64` → PostgreSQL `Integer`
229
+ - `float`, `double` → PostgreSQL `Float`
230
+ - `bool` → PostgreSQL `Boolean`
231
+ - `string` → PostgreSQL `String`
232
+ - `bytes` → PostgreSQL `LargeBinary`
233
+ - `Timestamp` (message) → PostgreSQL `DateTime`
234
+
235
+ ### `postgres_info.py` - Connection Information
236
+
237
+ Data class for PostgreSQL connection parameters:
238
+
239
+ ```python
240
+ from ry_pg_utils.postgres_info import PostgresInfo
241
+
242
+ db_info = PostgresInfo(
243
+ db_name="mydb",
244
+ host="localhost",
245
+ port=5432,
246
+ user="postgres",
247
+ password="secret"
248
+ )
249
+
250
+ # Get connection URI
251
+ uri = db_info.get_uri() # postgresql://postgres:secret@localhost:5432/mydb
252
+ ```
253
+
254
+ ### `parse_args.py` - Argument Parsing
255
+
256
+ Add PostgreSQL arguments to your argument parser:
257
+
258
+ ```python
259
+ import argparse
260
+ from ry_pg_utils.parse_args import add_postrgres_db_args
261
+
262
+ parser = argparse.ArgumentParser()
263
+ add_postrgres_db_args(parser)
264
+
265
+ args = parser.parse_args()
266
+ # Access: args.postgres_host, args.postgres_port, etc.
267
+ ```
268
+
269
+ ### `updater.py` - Database Configuration Updater
270
+
271
+ Dynamically update database connections based on configuration messages:
272
+
273
+ ```python
274
+ from ry_pg_utils.updater import PostgresDbUpdater
275
+
276
+ updater = PostgresDbUpdater(
277
+ redis_info=redis_info,
278
+ verbose=verbose,
279
+ backend_id="my_backend"
280
+ )
281
+
282
+ # Start listening for configuration updates
283
+ updater.run()
284
+ ```
285
+
286
+ ## Advanced Usage
287
+
288
+ ### Multi-Backend Support
289
+
290
+ When `add_backend_to_all` is enabled, all tables automatically get a `backend_id` column:
291
+
292
+ ```python
293
+ from ry_pg_utils.connect import set_backend_id, ManagedSession
294
+
295
+ # Set backend ID for current thread
296
+ set_backend_id("backend_1")
297
+
298
+ # All subsequent operations will include this backend_id
299
+ with ManagedSession(db="myapp") as session:
300
+ # Queries automatically filter by backend_id
301
+ result = session.execute("SELECT * FROM my_table")
302
+ ```
303
+
304
+ ### Custom Table Names
305
+
306
+ When `add_backend_to_tables` is enabled, table names are automatically suffixed:
307
+
308
+ ```python
309
+ from ry_pg_utils.connect import get_table_name
310
+
311
+ # Returns "events_my_backend" if add_backend_to_tables=True
312
+ table_name = get_table_name("events", backend_id="my_backend")
313
+ ```
314
+
315
+ ### ORM Base Class
316
+
317
+ Use the pre-configured base class for SQLAlchemy models:
318
+
319
+ ```python
320
+ from ry_pg_utils.connect import Base
321
+ from sqlalchemy import Column, Integer, String
322
+
323
+ class User(Base):
324
+ __tablename__ = 'users'
325
+
326
+ id = Column(Integer, primary_key=True)
327
+ name = Column(String(100))
328
+ email = Column(String(200))
329
+
330
+ # If add_backend_to_all=True, backend_id column is automatically added
331
+ ```
332
+
333
+ ## Error Handling
334
+
335
+ The library includes robust error handling:
336
+
337
+ ```python
338
+ from ry_pg_utils.connect import ManagedSession
339
+
340
+ with ManagedSession(db="myapp") as session:
341
+ if session is None:
342
+ # Connection failed, handle gracefully
343
+ print("Failed to establish database connection")
344
+ return
345
+
346
+ try:
347
+ session.execute("SELECT * FROM my_table")
348
+ except Exception as e:
349
+ # Session will automatically rollback
350
+ print(f"Query failed: {e}")
351
+ ```
352
+
353
+ ## Type Safety
354
+
355
+ The library is fully typed and includes a `py.typed` marker for mypy support:
356
+
357
+ ```bash
358
+ # Run type checking
359
+ mypy your_app.py
360
+ ```
361
+
362
+ ## Development
363
+
364
+ ### Setup Development Environment
365
+
366
+ ```bash
367
+ # Clone the repository
368
+ git clone https://github.com/yourusername/ry-pg-utils.git
369
+ cd ry-pg-utils
370
+
371
+ # Create virtual environment
372
+ python -m venv venv-dev
373
+ source venv-dev/bin/activate # On Windows: venv-dev\Scripts\activate
374
+
375
+ # Install dependencies
376
+ pip install -r packages/requirements-dev.txt
377
+ ```
378
+
379
+ ### Running Tests
380
+
381
+ ```bash
382
+ # Activate virtual environment
383
+ source venv-dev/bin/activate
384
+
385
+ # Run tests
386
+ python -m pytest test/
387
+ ```
388
+
389
+ ### Code Quality
390
+
391
+ The project uses several tools for code quality:
392
+
393
+ ```bash
394
+ # Format code
395
+ black ry_pg_utils/
396
+
397
+ # Type checking
398
+ mypy ry_pg_utils/
399
+
400
+ # Linting
401
+ pylint ry_pg_utils/
402
+
403
+ # Import sorting
404
+ isort ry_pg_utils/
405
+ ```
406
+
407
+ ## Examples
408
+
409
+ ### Complete Application Example
410
+
411
+ ```python
412
+ import argparse
413
+ from ry_pg_utils.parse_args import add_postrgres_db_args
414
+ from ry_pg_utils.connect import init_database, ManagedSession
415
+ from ry_pg_utils.postgres_info import PostgresInfo
416
+ from ry_pg_utils.dynamic_table import DynamicTableDb
417
+
418
+ def parse_args():
419
+ parser = argparse.ArgumentParser(description="My Database App")
420
+ add_postrgres_db_args(parser)
421
+ return parser.parse_args()
422
+
423
+ def main():
424
+ args = parse_args()
425
+
426
+ # Initialize database
427
+ db_info = PostgresInfo(
428
+ db_name=args.postgres_db,
429
+ host=args.postgres_host,
430
+ port=args.postgres_port,
431
+ user=args.postgres_user,
432
+ password=args.postgres_password
433
+ )
434
+
435
+ init_database(db_info, db_name="myapp")
436
+
437
+ # Use the database
438
+ with ManagedSession(db="myapp") as session:
439
+ if session:
440
+ result = session.execute("SELECT version()")
441
+ print(f"PostgreSQL version: {result.fetchone()[0]}")
442
+
443
+ if __name__ == "__main__":
444
+ main()
445
+ ```
446
+
447
+ ## License
448
+
449
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
450
+
451
+ ## Contributing
452
+
453
+ Contributions are welcome! Please feel free to submit a Pull Request.
454
+
455
+ 1. Fork the repository
456
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
457
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
458
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
459
+ 5. Open a Pull Request
460
+
461
+ ## Author
462
+
463
+ Ross Yeager - ryeager12@email.com
464
+
465
+ ## Changelog
466
+
467
+ ### Version 1.0.0
468
+ - Initial release
469
+ - Database connection management
470
+ - Dynamic table creation
471
+ - Multi-backend support
472
+ - Configuration system
473
+ - Protocol Buffer integration