diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index 2696970..9bb71e7 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -52,10 +52,15 @@ async def create_env(self) -> Union[EnvCreated, HTTPException]: env_repo.fetch_env(self.env) await env_repo.create_env() except PyMongoError as e: + logger.error( + f"controllers.environment.create_env: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to create environment in db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.environment.create_env: {exc_str}") @@ -89,10 +94,15 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: await env_repo.get_env_by_id(env_id) read_env = env_repo.env except PyMongoError as e: + logger.error( + f"controllers.environment.get_env_by_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to read environment from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.environment.get_env_by_id: {exc_str}") @@ -173,10 +183,15 @@ async def update_env_by_id( await env_repo.create_env() await env_repo.delete_env_by_id(env_id) except PyMongoError as e: + logger.error( + f"controllers.environment.update_env: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update environment from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.environment.update_env: {exc_str}") @@ -211,10 +226,15 @@ async def delete_env_by_id( async with EnvRepository() as env_repo: await env_repo.delete_env_by_id(env_id) except PyMongoError as e: + logger.error( + f"controllers.environment.delete_env: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to delete environment from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.environment.delete_env: {exc_str}") diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 3ed1bee..a3905ee 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -113,10 +113,13 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: rocket_option=self.rocket_option, ) except PyMongoError as e: + logger.error(f"controllers.flight.create_flight: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to create flight in db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.create_flight: {exc_str}") @@ -150,10 +153,15 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: await flight_repo.get_flight_by_id(flight_id) read_flight = flight_repo.flight except PyMongoError as e: + logger.error( + f"controllers.flight.get_flight_by_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to read flight from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.get_flight_by_id: {exc_str}") @@ -237,10 +245,13 @@ async def update_flight_by_id( ) await flight_repo.delete_flight_by_id(flight_id) except PyMongoError as e: + logger.error(f"controllers.flight.update_flight: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update flight in db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.update_flight: {exc_str}") @@ -285,10 +296,15 @@ async def update_env_by_flight_id( ) await flight_repo.delete_flight_by_id(flight_id) except PyMongoError as e: + logger.error( + f"controllers.flight.update_env_by_flight_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update environment from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.update_env: {exc_str}") @@ -335,10 +351,15 @@ async def update_rocket_by_flight_id( ) await flight_repo.delete_flight_by_id(flight_id) except PyMongoError as e: + logger.error( + f"controllers.flight.update_rocket_by_flight_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update rocket from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.update_rocket: {exc_str}") @@ -373,10 +394,13 @@ async def delete_flight_by_id( async with FlightRepository() as flight_repo: await flight_repo.delete_flight_by_id(flight_id) except PyMongoError as e: + logger.error(f"controllers.flight.delete_flight: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to delete flight from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.flight.delete_flight: {exc_str}") diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 111be8a..2ba8588 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -121,10 +121,13 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: motor_repo.fetch_motor(self.motor) await motor_repo.create_motor(motor_kind=self.motor_kind) except PyMongoError as e: + logger.error(f"controllers.motor.create_motor: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to create motor in db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.motor.create_motor: {exc_str}") @@ -158,10 +161,15 @@ async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: await motor_repo.get_motor_by_id(motor_id) read_motor = motor_repo.motor except PyMongoError as e: + logger.error( + f"controllers.motor.get_motor_by_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to read motor from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.motor.get_motor_by_id: {exc_str}") @@ -242,10 +250,13 @@ async def update_motor_by_id( await motor_repo.create_motor(motor_kind=self.motor_kind) await motor_repo.delete_motor_by_id(motor_id) except PyMongoError as e: + logger.error(f"controllers.motor.update_motor: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update motor in db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.motor.update_motor: {exc_str}") @@ -280,10 +291,13 @@ async def delete_motor_by_id( async with MotorRepository() as motor_repo: await motor_repo.delete_motor_by_id(motor_id) except PyMongoError as e: + logger.error(f"controllers.motor.delete_motor: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to delete motor from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.motor.delete_motor: {exc_str}") diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index 79468de..90b54a3 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -161,10 +161,13 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: motor_kind=self.motor_kind, ) except PyMongoError as e: + logger.error(f"controllers.rocket.create_rocket: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Failed to create rocket in the db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.rocket.create_rocket: {exc_str}") @@ -200,10 +203,15 @@ async def get_rocket_by_id( await rocket_repo.get_rocket_by_id(rocket_id) read_rocket = rocket_repo.rocket except PyMongoError as e: + logger.error( + f"controllers.rocket.get_rocket_by_id: PyMongoError {e}" + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to read rocket from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.rocket.get_rocket_by_id: {exc_str}") @@ -286,10 +294,13 @@ async def update_rocket_by_id( ) await rocket_repo.delete_rocket_by_id(rocket_id) except PyMongoError as e: + logger.error(f"controllers.rocket.update_rocket: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to update rocket in the db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.rocket.update_rocket: {exc_str}") @@ -324,10 +335,13 @@ async def delete_rocket_by_id( async with RocketRepository() as rocket_repo: await rocket_repo.delete_rocket_by_id(rocket_id) except PyMongoError as e: + logger.error(f"controllers.rocket.delete_rocket: PyMongoError {e}") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Failed to delete rocket from db", ) from e + except HTTPException as e: + raise e from e except Exception as e: exc_str = parse_error(e) logger.error(f"controllers.rocket.delete_rocket: {exc_str}") diff --git a/lib/repositories/repo.py b/lib/repositories/repo.py index 79f8849..39d217e 100644 --- a/lib/repositories/repo.py +++ b/lib/repositories/repo.py @@ -2,11 +2,20 @@ import threading from lib import logger from lib.secrets import Secrets +from fastapi import HTTPException, status from motor.motor_asyncio import AsyncIOMotorClient from pymongo.server_api import ServerApi from tenacity import retry, stop_after_attempt, wait_fixed +class RepositoryNotInitializedException(HTTPException): + def __init__(self): + super().__init__( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Repository not initialized. Please try again later.", + ) + + class Repository: """ Base class for all repositories (singleton) @@ -17,7 +26,9 @@ class Repository: _global_async_lock = asyncio.Lock() def __new__(cls, *args, **kwargs): - with cls._thread_lock: # Ensure thread safety + with ( + cls._thread_lock + ): # Ensure thread safety for singleton instance creation if cls not in cls._instances: instance = super(Repository, cls).__new__(cls) cls._instances[cls] = instance @@ -43,25 +54,26 @@ def _on_init_done(self, future): future.result() except Exception as e: logger.error("Initialization failed: %s", e, exc_info=True) + self._initialized = False + finally: self._initialized_event.set() - raise e - @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) + @retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) async def _async_init(self): - async with self._global_async_lock: # Coroutine-safe lock - self._initialize_connection() - self._initialized = True - self._initialized_event.set() + async with ( + self._global_async_lock + ): # Hybrid safe locks for initialization + with self._thread_lock: + self._initialize_connection() + self._initialized = True async def __aenter__(self): - await self._initialized_event.wait() + await self._initialized_event.wait() # Ensure initialization is complete return self async def __aexit__(self, exc_type, exc_value, traceback): await self._initialized_event.wait() - async with self._global_async_lock: # Coroutine-safe lock - with self._thread_lock: - await self._cleanup_instance() + await self._cleanup_instance() def _initialize_connection(self): try: @@ -86,41 +98,41 @@ def _initialize_connection(self): ) from e async def _cleanup_instance(self): - with self._thread_lock: - if hasattr(self, '_client'): - self._client.close() - logger.info("Connection closed for %s", self.__class__) - self._initialized = False - self._instances.pop(self.__class__, None) + async with ( + self._global_async_lock + ): # Hybrid safe locks for finalization + with self._thread_lock: + if hasattr(self, '_client'): + self._client.close() + logger.info("Connection closed for %s", self.__class__) + self._initialized = False + self._instances.pop(self.__class__, None) - @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) + @retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) def _get_connection_string(self): - with self._thread_lock: - if not getattr(self, '_initialized', False): - raise RepositoryNotInitializedException() - return self._connection_string + if not getattr(self, '_initialized', False): + raise RepositoryNotInitializedException() + return self._connection_string @property def connection_string(self): return self._get_connection_string() - @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) + @retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) def _get_client(self): - with self._thread_lock: - if not getattr(self, '_initialized', False): - raise RepositoryNotInitializedException() - return self._client + if not getattr(self, '_initialized', False): + raise RepositoryNotInitializedException() + return self._client @property def client(self): return self._get_client() - @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) + @retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) def _get_collection(self): - with self._thread_lock: - if not getattr(self, '_initialized', False): - raise RepositoryNotInitializedException() - return self._collection + if not getattr(self, '_initialized', False): + raise RepositoryNotInitializedException() + return self._collection @property def collection(self):