trovesuite 1.0.29__tar.gz → 1.0.31__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.
- {trovesuite-1.0.29/src/trovesuite.egg-info → trovesuite-1.0.31}/PKG-INFO +1 -1
- {trovesuite-1.0.29 → trovesuite-1.0.31}/pyproject.toml +2 -2
- {trovesuite-1.0.29 → trovesuite-1.0.31}/setup.py +1 -1
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/configs/database.py +115 -154
- {trovesuite-1.0.29 → trovesuite-1.0.31/src/trovesuite.egg-info}/PKG-INFO +1 -1
- {trovesuite-1.0.29 → trovesuite-1.0.31}/LICENSE +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/MANIFEST.in +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/README.md +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/requirements.txt +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/setup.cfg +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/auth_base.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/auth_controller.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/auth_read_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/auth_service.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/auth/auth_write_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/configs/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/configs/logging.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/configs/settings.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/entities/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/entities/health.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/entities/sh_response.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_base.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_controller.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_read_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_service.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_write_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/storage_base.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/storage_controller.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/storage_read_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/storage_service.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/storage/storage_write_dto.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/utils/__init__.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/utils/helper.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/utils/templates.py +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite.egg-info/SOURCES.txt +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite.egg-info/dependency_links.txt +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite.egg-info/not-zip-safe +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite.egg-info/requires.txt +0 -0
- {trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trovesuite
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.31
|
|
4
4
|
Summary: TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications
|
|
5
5
|
Home-page: https://dev.azure.com/brightgclt/trovesuite/_git/packages
|
|
6
6
|
Author: Bright Debrah Owusu
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "trovesuite"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.31"
|
|
8
8
|
description = "TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications"
|
|
9
9
|
authors = ["brightgclt <brightgclt@gmail.com>"]
|
|
10
10
|
license = "MIT"
|
|
@@ -58,7 +58,7 @@ Documentation = "https://dev.azure.com/brightgclt/trovesuite/_git/packages"
|
|
|
58
58
|
|
|
59
59
|
[project]
|
|
60
60
|
name = "trovesuite"
|
|
61
|
-
version = "1.0.
|
|
61
|
+
version = "1.0.31"
|
|
62
62
|
description = "TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications"
|
|
63
63
|
readme = "README.md"
|
|
64
64
|
license = {text = "MIT"}
|
|
@@ -15,7 +15,7 @@ with open("pyproject.toml", "r", encoding="utf-8") as fh:
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
name="trovesuite",
|
|
18
|
-
version="1.0.
|
|
18
|
+
version="1.0.31",
|
|
19
19
|
author="Bright Debrah Owusu",
|
|
20
20
|
author_email="owusu.debrah@deladetech.com",
|
|
21
21
|
description="TroveSuite services package providing authentication, authorization, notifications, and other enterprise services for TroveSuite applications",
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Database configuration and connection management
|
|
3
|
+
|
|
4
|
+
FIXED VERSION - Removed pool reinitialization during runtime to prevent "unkeyed connection" errors.
|
|
5
|
+
Key changes:
|
|
6
|
+
- Pool created once at startup, never recreated during runtime
|
|
7
|
+
- Removed connection validation queries that consume pool connections
|
|
8
|
+
- Increased default pool size to 2 (safe for Azure Basic tier)
|
|
9
|
+
- Simplified get_db_connection() - let pool.getconn() block naturally
|
|
10
|
+
- Removed _recover_connection_pool() and _is_pool_valid() runtime checks
|
|
11
|
+
|
|
12
|
+
NOTE: Database initialization is NOT automatic for package version.
|
|
13
|
+
You must call initialize_database() explicitly in your application startup.
|
|
14
|
+
Example in FastAPI:
|
|
15
|
+
@app.on_event("startup")
|
|
16
|
+
async def startup_event():
|
|
17
|
+
initialize_database()
|
|
3
18
|
"""
|
|
4
19
|
from contextlib import contextmanager
|
|
5
20
|
from typing import Generator, Optional
|
|
@@ -12,7 +27,7 @@ from .logging import get_logger
|
|
|
12
27
|
|
|
13
28
|
logger = get_logger("database")
|
|
14
29
|
|
|
15
|
-
# Database connection pool
|
|
30
|
+
# Database connection pool - created once at startup, never replaced during runtime
|
|
16
31
|
_connection_pool: Optional[psycopg2.pool.ThreadedConnectionPool] = None
|
|
17
32
|
_initialization_lock = threading.Lock()
|
|
18
33
|
|
|
@@ -21,10 +36,14 @@ class DatabaseConfig:
|
|
|
21
36
|
"""Database configuration and connection management"""
|
|
22
37
|
|
|
23
38
|
def __init__(self):
|
|
39
|
+
import os
|
|
24
40
|
self.settings = db_settings
|
|
25
41
|
self.database_url = self.settings.database_url
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
# Safe pool size for Azure Basic tier (B_Standard_B1ms)
|
|
43
|
+
# With 2 workers and pool_size=2: 2 × 2 = 4 connections (very safe)
|
|
44
|
+
# With 4 workers and pool_size=2: 4 × 2 = 8 connections (still safe, well under 50 limit)
|
|
45
|
+
# Can be overridden with DB_POOL_SIZE environment variable
|
|
46
|
+
self.pool_size = int(os.getenv("DB_POOL_SIZE", "2"))
|
|
28
47
|
|
|
29
48
|
def get_connection_params(self) -> dict:
|
|
30
49
|
"""Get database connection parameters"""
|
|
@@ -37,7 +56,7 @@ class DatabaseConfig:
|
|
|
37
56
|
"keepalives_idle": 30,
|
|
38
57
|
"keepalives_interval": 10,
|
|
39
58
|
"keepalives_count": 5,
|
|
40
|
-
"connect_timeout":
|
|
59
|
+
"connect_timeout": 30 # Increased timeout for Azure PostgreSQL
|
|
41
60
|
}
|
|
42
61
|
|
|
43
62
|
# fallback to individual DB_* variables
|
|
@@ -59,19 +78,43 @@ class DatabaseConfig:
|
|
|
59
78
|
def create_connection_pool(self) -> psycopg2.pool.ThreadedConnectionPool:
|
|
60
79
|
"""Create a connection pool for psycopg2"""
|
|
61
80
|
try:
|
|
81
|
+
import os
|
|
82
|
+
dsn = os.getenv("DATABASE_URL", "") or str(self.database_url or "")
|
|
83
|
+
is_azure = "database.azure.com" in dsn.lower()
|
|
84
|
+
pool_size = self.pool_size
|
|
85
|
+
|
|
62
86
|
pool = psycopg2.pool.ThreadedConnectionPool(
|
|
63
87
|
minconn=1,
|
|
64
|
-
maxconn=
|
|
88
|
+
maxconn=pool_size,
|
|
65
89
|
**self.get_connection_params()
|
|
66
90
|
)
|
|
67
|
-
logger.info(
|
|
91
|
+
logger.info(
|
|
92
|
+
f"Database connection pool created with {pool_size} connections "
|
|
93
|
+
f"(Azure: {is_azure}, DB_POOL_SIZE: {self.pool_size})"
|
|
94
|
+
)
|
|
68
95
|
return pool
|
|
96
|
+
except psycopg2.OperationalError as e:
|
|
97
|
+
error_str = str(e).lower()
|
|
98
|
+
if any(keyword in error_str for keyword in ["connection", "slot", "limit", "exhausted", "too many"]):
|
|
99
|
+
logger.error("⚠️ Database connection limit reached!")
|
|
100
|
+
logger.error(" Possible causes:")
|
|
101
|
+
logger.error(" 1. Too many connections from multiple replicas/workers")
|
|
102
|
+
logger.error(" 2. Pool size too high (DB_POOL_SIZE environment variable)")
|
|
103
|
+
logger.error(" 3. Too many Gunicorn workers (GUNICORN_WORKERS environment variable)")
|
|
104
|
+
logger.error(" 4. Connections not being properly returned to pool")
|
|
105
|
+
logger.error(" Solutions:")
|
|
106
|
+
logger.error(" - Set DB_POOL_SIZE=2 (current default)")
|
|
107
|
+
logger.error(" - Reduce GUNICORN_WORKERS (default: 4)")
|
|
108
|
+
logger.error(" - Consider using PgBouncer for connection pooling")
|
|
109
|
+
logger.error(" - Upgrade to a higher PostgreSQL tier if needed")
|
|
110
|
+
logger.error(f"Failed to create database connection pool: {str(e)}")
|
|
111
|
+
raise
|
|
69
112
|
except Exception as e:
|
|
70
113
|
logger.error(f"Failed to create database connection pool: {str(e)}")
|
|
71
114
|
raise
|
|
72
115
|
|
|
73
116
|
def test_connection(self) -> bool:
|
|
74
|
-
"""Test database connection"""
|
|
117
|
+
"""Test database connection (only used at startup)"""
|
|
75
118
|
try:
|
|
76
119
|
with psycopg2.connect(**self.get_connection_params()) as conn:
|
|
77
120
|
with conn.cursor() as cursor:
|
|
@@ -91,190 +134,104 @@ db_config = DatabaseConfig()
|
|
|
91
134
|
|
|
92
135
|
|
|
93
136
|
def initialize_database():
|
|
94
|
-
"""
|
|
137
|
+
"""
|
|
138
|
+
Initialize database connections and pool.
|
|
139
|
+
This should ONLY be called at application startup.
|
|
140
|
+
Pool is created once and never recreated during runtime.
|
|
141
|
+
|
|
142
|
+
NOTE: For package version, this must be called explicitly in application startup.
|
|
143
|
+
"""
|
|
95
144
|
global _connection_pool
|
|
96
145
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
except Exception as e:
|
|
103
|
-
logger.warning(f"Error closing existing pool: {str(e)}")
|
|
104
|
-
_connection_pool = None
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
# Test connection first
|
|
108
|
-
if not db_config.test_connection():
|
|
109
|
-
raise Exception("Database connection test failed")
|
|
110
|
-
|
|
111
|
-
# Create connection pool
|
|
112
|
-
_connection_pool = db_config.create_connection_pool()
|
|
146
|
+
with _initialization_lock:
|
|
147
|
+
# If pool already exists, don't recreate it
|
|
148
|
+
if _connection_pool is not None:
|
|
149
|
+
logger.warning("Database pool already initialized, skipping reinitialization")
|
|
150
|
+
return
|
|
113
151
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
logger.info("✅ Database initialization completed successfully")
|
|
119
|
-
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger.error(f"❌ Database initialization failed: {str(e)}")
|
|
122
|
-
_connection_pool = None # Ensure pool is None on failure
|
|
123
|
-
raise
|
|
124
|
-
|
|
152
|
+
try:
|
|
153
|
+
# Test connection first (only at startup)
|
|
154
|
+
if not db_config.test_connection():
|
|
155
|
+
raise Exception("Database connection test failed")
|
|
125
156
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# Check if pool has the necessary internal structures
|
|
133
|
-
if not hasattr(pool, '_pool'):
|
|
134
|
-
return False
|
|
135
|
-
if pool._pool is None:
|
|
136
|
-
return False
|
|
137
|
-
# Additional check: verify pool has connection parameters
|
|
138
|
-
if not hasattr(pool, '_kwargs'):
|
|
139
|
-
return False
|
|
140
|
-
return True
|
|
141
|
-
except (AttributeError, Exception) as e:
|
|
142
|
-
logger.debug(f"Pool validation check: {str(e)}")
|
|
143
|
-
return False
|
|
157
|
+
# Create connection pool (only once at startup)
|
|
158
|
+
_connection_pool = db_config.create_connection_pool()
|
|
159
|
+
|
|
160
|
+
# Verify pool was created successfully
|
|
161
|
+
if _connection_pool is None:
|
|
162
|
+
raise Exception("Connection pool creation returned None")
|
|
144
163
|
|
|
164
|
+
logger.info("✅ Database initialization completed successfully")
|
|
145
165
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
with _initialization_lock:
|
|
152
|
-
# Double-check after acquiring lock
|
|
153
|
-
if _connection_pool is not None and _is_pool_valid(_connection_pool):
|
|
154
|
-
return True
|
|
155
|
-
|
|
156
|
-
# Close invalid pool if it exists
|
|
157
|
-
if _connection_pool is not None:
|
|
158
|
-
try:
|
|
159
|
-
_connection_pool.closeall()
|
|
160
|
-
logger.info("Closed invalid connection pool")
|
|
161
|
-
except Exception as e:
|
|
162
|
-
logger.warning(f"Error closing invalid pool: {str(e)}")
|
|
163
|
-
_connection_pool = None
|
|
164
|
-
|
|
165
|
-
# Retry with exponential backoff
|
|
166
|
-
max_retries = 3
|
|
167
|
-
base_delay = 1 # Start with 1 second
|
|
168
|
-
|
|
169
|
-
for attempt in range(1, max_retries + 1):
|
|
170
|
-
try:
|
|
171
|
-
logger.warning(f"Attempting to reinitialize connection pool (attempt {attempt}/{max_retries})...")
|
|
172
|
-
initialize_database()
|
|
173
|
-
|
|
174
|
-
if _connection_pool is not None and _is_pool_valid(_connection_pool):
|
|
175
|
-
logger.info(f"✅ Connection pool reinitialized successfully (attempt {attempt})")
|
|
176
|
-
return True
|
|
177
|
-
else:
|
|
178
|
-
logger.warning(f"Pool initialized but validation failed (attempt {attempt})")
|
|
179
|
-
|
|
180
|
-
except Exception as e:
|
|
181
|
-
logger.error(f"Pool reinitialization attempt {attempt} failed: {str(e)}")
|
|
182
|
-
if attempt < max_retries:
|
|
183
|
-
delay = base_delay * (2 ** (attempt - 1)) # Exponential backoff: 1s, 2s, 4s
|
|
184
|
-
logger.info(f"Retrying in {delay} seconds...")
|
|
185
|
-
time.sleep(delay)
|
|
186
|
-
|
|
187
|
-
logger.error("❌ Failed to reinitialize connection pool after all retries")
|
|
188
|
-
return False
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"❌ Database initialization failed: {str(e)}")
|
|
168
|
+
_connection_pool = None # Ensure pool is None on failure
|
|
169
|
+
raise
|
|
189
170
|
|
|
190
171
|
|
|
191
172
|
def get_connection_pool() -> psycopg2.pool.ThreadedConnectionPool:
|
|
192
|
-
"""
|
|
173
|
+
"""
|
|
174
|
+
Get the database connection pool.
|
|
175
|
+
Pool must be initialized at startup. This function will raise if pool is None.
|
|
176
|
+
"""
|
|
193
177
|
global _connection_pool
|
|
194
178
|
|
|
195
|
-
|
|
196
|
-
if _connection_pool is not None and _is_pool_valid(_connection_pool):
|
|
197
|
-
return _connection_pool
|
|
198
|
-
|
|
199
|
-
# Pool is None or invalid, attempt recovery
|
|
200
|
-
if not _recover_connection_pool():
|
|
179
|
+
if _connection_pool is None:
|
|
201
180
|
error_msg = (
|
|
202
|
-
"Database connection pool is
|
|
203
|
-
"
|
|
204
|
-
"2. Network connectivity issues\n"
|
|
205
|
-
"3. Database credentials are incorrect\n"
|
|
206
|
-
"4. Connection pool exhausted or closed\n"
|
|
207
|
-
"5. Database initialization failed\n"
|
|
208
|
-
"Please check the startup logs and database status."
|
|
181
|
+
"Database connection pool is not initialized. "
|
|
182
|
+
"Please ensure initialize_database() was called at application startup."
|
|
209
183
|
)
|
|
210
184
|
logger.error(error_msg)
|
|
211
185
|
raise Exception(error_msg)
|
|
212
186
|
|
|
213
|
-
if _connection_pool is None:
|
|
214
|
-
error_msg = "Connection pool recovery completed but pool is still None"
|
|
215
|
-
logger.error(error_msg)
|
|
216
|
-
raise Exception(error_msg)
|
|
217
|
-
|
|
218
187
|
return _connection_pool
|
|
219
188
|
|
|
220
189
|
|
|
221
|
-
def _validate_connection(conn) -> bool:
|
|
222
|
-
"""Validate if a connection is still alive"""
|
|
223
|
-
try:
|
|
224
|
-
# Check if connection is closed first
|
|
225
|
-
if conn.closed:
|
|
226
|
-
return False
|
|
227
|
-
|
|
228
|
-
# Test if connection is alive with a simple query
|
|
229
|
-
with conn.cursor() as cursor:
|
|
230
|
-
cursor.execute("SELECT 1")
|
|
231
|
-
cursor.fetchone()
|
|
232
|
-
return True
|
|
233
|
-
except (psycopg2.OperationalError, psycopg2.InterfaceError, psycopg2.DatabaseError) as e:
|
|
234
|
-
logger.warning(f"Connection validation failed: {str(e)}")
|
|
235
|
-
return False
|
|
236
|
-
except Exception as e:
|
|
237
|
-
logger.warning(f"Unexpected error during connection validation: {str(e)}")
|
|
238
|
-
return False
|
|
239
|
-
|
|
240
|
-
|
|
241
190
|
@contextmanager
|
|
242
191
|
def get_db_connection():
|
|
243
|
-
"""
|
|
192
|
+
"""
|
|
193
|
+
Get a database connection from the pool (context manager).
|
|
194
|
+
|
|
195
|
+
This is simplified - we let pool.getconn() block naturally.
|
|
196
|
+
No retries, no validation queries, no pool recovery.
|
|
197
|
+
"""
|
|
244
198
|
pool = get_connection_pool()
|
|
245
199
|
conn = None
|
|
246
200
|
try:
|
|
201
|
+
# Get connection from pool - this will block if pool is exhausted
|
|
202
|
+
# That's the correct behavior - let backpressure happen naturally
|
|
247
203
|
conn = pool.getconn()
|
|
248
|
-
|
|
249
|
-
# Validate connection before using it
|
|
250
|
-
if not _validate_connection(conn):
|
|
251
|
-
logger.warning("Stale connection detected, getting new connection")
|
|
252
|
-
pool.putconn(conn, close=True)
|
|
253
|
-
conn = pool.getconn()
|
|
254
|
-
|
|
255
204
|
logger.debug("Database connection acquired from pool")
|
|
256
205
|
yield conn
|
|
257
206
|
except Exception as e:
|
|
258
|
-
|
|
259
|
-
if conn:
|
|
207
|
+
# If connection exists and isn't closed, rollback transaction
|
|
208
|
+
if conn and not conn.closed:
|
|
260
209
|
try:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
logger.warning(f"Could not rollback closed connection: {str(rollback_error)}")
|
|
210
|
+
conn.rollback()
|
|
211
|
+
except Exception as rollback_error:
|
|
212
|
+
logger.warning(f"Could not rollback transaction: {str(rollback_error)}")
|
|
213
|
+
# Re-raise the exception - don't retry here
|
|
266
214
|
raise
|
|
267
215
|
finally:
|
|
216
|
+
# Always return connection to pool
|
|
268
217
|
if conn:
|
|
269
218
|
try:
|
|
270
|
-
# If connection is broken, close it instead of returning to pool
|
|
271
219
|
if conn.closed:
|
|
220
|
+
# If connection is closed, tell pool to close it instead of returning
|
|
272
221
|
pool.putconn(conn, close=True)
|
|
273
222
|
else:
|
|
223
|
+
# Return connection to pool normally
|
|
274
224
|
pool.putconn(conn)
|
|
275
225
|
logger.debug("Database connection returned to pool")
|
|
276
226
|
except Exception as put_error:
|
|
227
|
+
# Log error but don't fail - connection will be cleaned up by pool
|
|
277
228
|
logger.error(f"Error returning connection to pool: {str(put_error)}")
|
|
229
|
+
# Try to close connection manually as last resort
|
|
230
|
+
try:
|
|
231
|
+
if not conn.closed:
|
|
232
|
+
conn.close()
|
|
233
|
+
except Exception:
|
|
234
|
+
pass
|
|
278
235
|
|
|
279
236
|
|
|
280
237
|
@contextmanager
|
|
@@ -347,6 +304,7 @@ class DatabaseManager:
|
|
|
347
304
|
cursor.execute("INSERT INTO table2 ...")
|
|
348
305
|
# Auto-commits on success, auto-rollbacks on exception
|
|
349
306
|
"""
|
|
307
|
+
# Use get_db_connection() instead of directly accessing pool
|
|
350
308
|
with get_db_connection() as conn:
|
|
351
309
|
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
|
352
310
|
try:
|
|
@@ -370,7 +328,10 @@ class DatabaseManager:
|
|
|
370
328
|
|
|
371
329
|
@staticmethod
|
|
372
330
|
def health_check() -> dict:
|
|
373
|
-
"""
|
|
331
|
+
"""
|
|
332
|
+
Perform database health check.
|
|
333
|
+
Health checks are allowed to fail - they don't attempt to repair the pool.
|
|
334
|
+
"""
|
|
374
335
|
try:
|
|
375
336
|
with get_db_cursor() as cursor:
|
|
376
337
|
cursor.execute("SELECT version(), current_database(), current_user")
|
|
@@ -406,9 +367,9 @@ class DatabaseManager:
|
|
|
406
367
|
}
|
|
407
368
|
|
|
408
369
|
|
|
409
|
-
# NOTE: Database initialization is NOT automatic
|
|
370
|
+
# NOTE: Database initialization is NOT automatic for package version
|
|
410
371
|
# You must call initialize_database() explicitly in your application startup
|
|
411
372
|
# Example in FastAPI:
|
|
412
373
|
# @app.on_event("startup")
|
|
413
374
|
# async def startup_event():
|
|
414
|
-
# initialize_database()
|
|
375
|
+
# initialize_database()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trovesuite
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.31
|
|
4
4
|
Summary: TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications
|
|
5
5
|
Home-page: https://dev.azure.com/brightgclt/trovesuite/_git/packages
|
|
6
6
|
Author: Bright Debrah Owusu
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_controller.py
RENAMED
|
File without changes
|
{trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_read_dto.py
RENAMED
|
File without changes
|
|
File without changes
|
{trovesuite-1.0.29 → trovesuite-1.0.31}/src/trovesuite/notification/notification_write_dto.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|