trovesuite 1.0.28__tar.gz → 1.0.29__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 (43) hide show
  1. {trovesuite-1.0.28/src/trovesuite.egg-info → trovesuite-1.0.29}/PKG-INFO +1 -1
  2. {trovesuite-1.0.28 → trovesuite-1.0.29}/pyproject.toml +2 -2
  3. {trovesuite-1.0.28 → trovesuite-1.0.29}/setup.py +1 -1
  4. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/__init__.py +1 -1
  5. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/configs/database.py +114 -11
  6. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/configs/settings.py +0 -5
  7. {trovesuite-1.0.28 → trovesuite-1.0.29/src/trovesuite.egg-info}/PKG-INFO +1 -1
  8. {trovesuite-1.0.28 → trovesuite-1.0.29}/LICENSE +0 -0
  9. {trovesuite-1.0.28 → trovesuite-1.0.29}/MANIFEST.in +0 -0
  10. {trovesuite-1.0.28 → trovesuite-1.0.29}/README.md +0 -0
  11. {trovesuite-1.0.28 → trovesuite-1.0.29}/requirements.txt +0 -0
  12. {trovesuite-1.0.28 → trovesuite-1.0.29}/setup.cfg +0 -0
  13. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/__init__.py +0 -0
  14. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/auth_base.py +0 -0
  15. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/auth_controller.py +0 -0
  16. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/auth_read_dto.py +0 -0
  17. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/auth_service.py +0 -0
  18. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/auth/auth_write_dto.py +0 -0
  19. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/configs/__init__.py +0 -0
  20. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/configs/logging.py +0 -0
  21. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/entities/__init__.py +0 -0
  22. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/entities/health.py +0 -0
  23. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/entities/sh_response.py +0 -0
  24. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/__init__.py +0 -0
  25. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/notification_base.py +0 -0
  26. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/notification_controller.py +0 -0
  27. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/notification_read_dto.py +0 -0
  28. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/notification_service.py +0 -0
  29. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/notification/notification_write_dto.py +0 -0
  30. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/__init__.py +0 -0
  31. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/storage_base.py +0 -0
  32. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/storage_controller.py +0 -0
  33. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/storage_read_dto.py +0 -0
  34. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/storage_service.py +0 -0
  35. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/storage/storage_write_dto.py +0 -0
  36. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/utils/__init__.py +0 -0
  37. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/utils/helper.py +0 -0
  38. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite/utils/templates.py +0 -0
  39. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite.egg-info/SOURCES.txt +0 -0
  40. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite.egg-info/dependency_links.txt +0 -0
  41. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite.egg-info/not-zip-safe +0 -0
  42. {trovesuite-1.0.28 → trovesuite-1.0.29}/src/trovesuite.egg-info/requires.txt +0 -0
  43. {trovesuite-1.0.28 → trovesuite-1.0.29}/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.28
3
+ Version: 1.0.29
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.28"
7
+ version = "1.0.29"
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.28"
61
+ version = "1.0.29"
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.28",
18
+ version="1.0.29",
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",
@@ -11,7 +11,7 @@ from .notification import NotificationService
11
11
  from .storage import StorageService
12
12
  from .utils import Helper
13
13
 
14
- __version__ = "1.0.20"
14
+ __version__ = "1.0.29"
15
15
  __author__ = "Bright Debrah Owusu"
16
16
  __email__ = "owusu.debrah@deladetech.com"
17
17
 
@@ -6,6 +6,7 @@ from typing import Generator, Optional
6
6
  import psycopg2
7
7
  import psycopg2.pool
8
8
  from psycopg2.extras import RealDictCursor
9
+ import threading
9
10
  from .settings import db_settings
10
11
  from .logging import get_logger
11
12
 
@@ -13,6 +14,7 @@ logger = get_logger("database")
13
14
 
14
15
  # Database connection pool
15
16
  _connection_pool: Optional[psycopg2.pool.ThreadedConnectionPool] = None
17
+ _initialization_lock = threading.Lock()
16
18
 
17
19
 
18
20
  class DatabaseConfig:
@@ -91,6 +93,15 @@ db_config = DatabaseConfig()
91
93
  def initialize_database():
92
94
  """Initialize database connections and pool"""
93
95
  global _connection_pool
96
+
97
+ # Close existing pool if it exists (cleanup before reinitializing)
98
+ if _connection_pool is not None:
99
+ try:
100
+ _connection_pool.closeall()
101
+ logger.info("Closed existing connection pool before reinitialization")
102
+ except Exception as e:
103
+ logger.warning(f"Error closing existing pool: {str(e)}")
104
+ _connection_pool = None
94
105
 
95
106
  try:
96
107
  # Test connection first
@@ -99,39 +110,131 @@ def initialize_database():
99
110
 
100
111
  # Create connection pool
101
112
  _connection_pool = db_config.create_connection_pool()
113
+
114
+ # Verify pool was created successfully
115
+ if _connection_pool is None:
116
+ raise Exception("Connection pool creation returned None")
102
117
 
103
- logger.info("Database initialization completed successfully")
118
+ logger.info("Database initialization completed successfully")
104
119
 
105
120
  except Exception as e:
106
- logger.error(f"Database initialization failed: {str(e)}")
121
+ logger.error(f"Database initialization failed: {str(e)}")
122
+ _connection_pool = None # Ensure pool is None on failure
107
123
  raise
108
124
 
109
125
 
126
+ def _is_pool_valid(pool) -> bool:
127
+ """Check if the connection pool is valid and usable"""
128
+ if pool is None:
129
+ return False
130
+ try:
131
+ # ThreadedConnectionPool doesn't expose a direct "closed" attribute
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
144
+
145
+
146
+ def _recover_connection_pool() -> bool:
147
+ """Attempt to recover the connection pool with retry logic"""
148
+ global _connection_pool, _initialization_lock
149
+ import time
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
189
+
190
+
110
191
  def get_connection_pool() -> psycopg2.pool.ThreadedConnectionPool:
111
- """Get the database connection pool"""
192
+ """Get the database connection pool, with automatic reinitialization if needed"""
112
193
  global _connection_pool
113
- if _connection_pool is None:
194
+
195
+ # Fast path: pool exists and is valid
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():
114
201
  error_msg = (
115
- "Database not initialized. This usually means:\n"
116
- "1. Missing or incorrect .env file in app/ directory\n"
117
- "2. Database credentials are wrong\n"
118
- "3. Database container is not running\n"
119
- "4. Database initialization failed during startup\n"
120
- "Please check the startup logs for more details."
202
+ "Database connection pool is unavailable. This usually means:\n"
203
+ "1. Database server is unreachable or down\n"
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."
121
209
  )
122
210
  logger.error(error_msg)
123
211
  raise Exception(error_msg)
212
+
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
+
124
218
  return _connection_pool
125
219
 
126
220
 
127
221
  def _validate_connection(conn) -> bool:
128
222
  """Validate if a connection is still alive"""
129
223
  try:
224
+ # Check if connection is closed first
225
+ if conn.closed:
226
+ return False
227
+
130
228
  # Test if connection is alive with a simple query
131
229
  with conn.cursor() as cursor:
132
230
  cursor.execute("SELECT 1")
231
+ cursor.fetchone()
133
232
  return True
134
- except (psycopg2.OperationalError, psycopg2.InterfaceError):
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)}")
135
238
  return False
136
239
 
137
240
 
@@ -10,11 +10,6 @@ class Settings:
10
10
  DB_PORT: str = os.getenv("DB_PORT")
11
11
  DB_PASSWORD: str = os.getenv("DB_PASSWORD")
12
12
 
13
- # CORS Endpoint
14
- # LOCAL_HOST_URL = os.getenv("LOCAL_HOST_URL")
15
- # FRONTEND_SERVER_URL_1 = os.getenv("FRONTEND_SERVER_URL")
16
- # FRONTEND_SERVER_URL_2 = os.getenv("FRONTEND_SERVER_URL")
17
-
18
13
  # Application settings
19
14
  DEBUG: bool = os.getenv("DEBUG", "True").lower() in ("true",1)
20
15
  APP_NAME: str = os.getenv("APP_NAME", "Python Template API")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trovesuite
3
- Version: 1.0.28
3
+ Version: 1.0.29
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