diff --git a/README.md b/README.md index 438083b..4b7569c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - Stores simulation input data in mongo-db ## Setup -- [Install python3](https://www.python.org/downloads/) +- [Install python3](https://www.python.org/downloads/) 3.11.5 or above - [install mongodb-atlas](https://www.mongodb.com/try/download/community) - Install dependencies `python3 -m pip install -r requirements.txt` diff --git a/lib/api.py b/lib/api.py index 789c9e8..595a131 100644 --- a/lib/api.py +++ b/lib/api.py @@ -1,12 +1,14 @@ """ This is the main API file for the RocketPy API. """ +import logging from typing import Any, Dict -from fastapi import FastAPI, status +from fastapi import FastAPI, Request, status +from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.utils import get_openapi -from fastapi.responses import RedirectResponse +from fastapi.responses import RedirectResponse, JSONResponse from lib.views.flight import FlightSummary, FlightCreated, FlightUpdated, FlightDeleted, FlightPickle from lib.views.environment import EnvSummary, EnvCreated, EnvUpdated, EnvDeleted, EnvPickle @@ -14,8 +16,8 @@ from lib.views.motor import MotorSummary, MotorCreated, MotorUpdated, MotorDeleted, MotorPickle from lib.models.environment import Env from lib.models.flight import Flight -from lib.models.rocket import Rocket -from lib.models.motor import Motor +from lib.models.rocket import Rocket, RocketOptions +from lib.models.motor import Motor, MotorKinds, MotorEngines from lib.controllers.flight import FlightController from lib.controllers.environment import EnvController from lib.controllers.rocket import RocketController @@ -70,140 +72,176 @@ async def main_page(): # Flight routes @app.post("/flights/", tags=["FLIGHT"]) -async def create_flight(flight: Flight) -> "FlightCreated": +async def create_flight(flight: Flight, rocket_option: RocketOptions, motor_kind: MotorKinds) -> "FlightCreated": """ - Creates a new flight. + Creates a new flight - Args: - Pydantic flight object as a JSON request according to API docs. + ## Args + + ``` Flight object as JSON ``` - Returns: + ## Returns + HTTP 200 { "message": "Flight created successfully.", id: flight_id_hash } - Raises: + ## Raises + HTTP 422 Unprocessable Entity: If API is unable to parse flight data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to create flight in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await FlightController(flight).create_flight() + return await FlightController(flight, rocket_option, motor_kind).create_flight() @app.get("/flights/{flight_id}", tags=["FLIGHT"]) async def read_flight(flight_id: int) -> "Flight": """ - Reads a flight. + Reads a flight - Args: - flight_id: Flight ID hash. + ## Args + + ``` flight_id: Flight ID hash ``` + + ## Returns - Returns: - Pydantic flight object as JSON. + ``` Flight object as JSON ``` - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. """ return await FlightController.get_flight(flight_id) @app.get("/flights/rocketpy/{flight_id}", tags=["FLIGHT"]) async def read_rocketpy_flight(flight_id: int) -> "FlightPickle": """ - Reads a rocketpy flight object. + Reads a rocketpy flight object - Args: - flight_id: Flight ID hash. + ## Args + + ``` flight_id: Flight ID hash. ``` - Returns: - RocketPy flight object encoded as a jsonpickle string. + ## Returns - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ``` RocketPy flight object as jsonpickle string ``` + + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. """ return await FlightController.get_rocketpy_flight(flight_id) @app.put("/flights/{flight_id}/env", tags=["FLIGHT"]) async def update_flight_env(flight_id: int, env: Env) -> "FlightUpdated": """ - Updates flight environment. + Updates flight environment - Args: - flight_id: Flight ID hash. - env: Pydantic env object as JSON request according to API docs. + ## Args + + ``` + flight_id: Flight ID hash + env: env object as JSON + ``` + + ## Returns - Returns: HTTP 200 { "message": "Flight updated successfully.", new_flight_id: new_flight_id_hash } - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse env data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update flight in mongoDB or valid parameter type/structure provided but content is breaking the API. """ return await FlightController.update_env(flight_id, env) @app.put("/flights/{flight_id}/rocket", tags=["FLIGHT"]) -async def update_flight_rocket(flight_id: int, rocket: Rocket) -> "FlightUpdated": +async def update_flight_rocket(flight_id: int, rocket: Rocket, rocket_option: RocketOptions, motor_kind: MotorKinds) -> "FlightUpdated": """ Updates flight rocket. - Args: + ## Args + + ``` flight_id: Flight ID hash. - rocket: Pydantic rocket object as JSON request according to API docs. + rocket: Rocket object as JSON + ``` + + ## Returns - Returns: HTTP 200 { "message": "Flight updated successfully.", new_flight_id: new_flight_id_hash } - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse rocket data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update flight in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await FlightController.update_rocket(flight_id, rocket) + return await FlightController.update_rocket(flight_id, rocket, rocket_option, motor_kind) @app.put("/flights/{flight_id}", tags=["FLIGHT"]) -async def update_flight(flight_id: int, flight: Flight) -> "FlightUpdated": +async def update_flight(flight_id: int, flight: Flight, rocket_option: RocketOptions, motor_kind: MotorKinds) -> "FlightUpdated": """ - Updates whole flight. + Updates Flight object - Args: + ## Args + ``` flight_id: Flight ID hash. - flight: Pydantic flight object as JSON request according to API docs. + flight: Flight object as JSON + ``` + + ## Returns - Returns: HTTP 200 { "message": "Flight updated successfully.", new_flight_id: new_flight_id_hash } - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse flight data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update flight in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await FlightController(flight).update_flight(flight_id) + return await FlightController(flight, rocket_option, motor_kind).update_flight(flight_id) @app.delete("/flights/{flight_id}", tags=["FLIGHT"]) async def delete_flight(flight_id: int) -> "FlightDeleted": """ - Deletes a flight. + Deletes a flight - Args: - flight_id: Flight ID hash. + ## Args + + ``` flight_id: Flight ID hash ``` + + ## Returns - Returns: HTTP 200 { "message": "Flight deleted successfully." } - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. """ return await FlightController.delete_flight(flight_id) @app.get("/flights/{flight_id}/simulate", tags=["FLIGHT"]) async def simulate_flight(flight_id: int) -> "FlightSummary": """ - Simulates a flight. + Simulates a flight - Args: - flight_id: Flight ID hash. + ## Args + + ``` flight_id: Flight ID hash ``` + + ## Returns - Returns: - HTTP 200 pydantic flight summary object as JSON. + ``` Flight summary as JSON ``` - Raises: - HTTP 404 Not Found: If flight_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If flight_id does not exist at database. """ return await FlightController.simulate(flight_id) @@ -211,16 +249,20 @@ async def simulate_flight(flight_id: int) -> "FlightSummary": @app.post("/environments/", tags=["ENVIRONMENT"]) async def create_env(env: Env) -> "EnvCreated": """ - Creates a new environment. + Creates a new environment + + ## Args - Args: - Pydantic env object as a JSON request according to API docs. + ``` Env object as a JSON ``` - Returns: + ## Returns + HTTP 200 { "message": "Environment created successfully.", id: env_id_hash } - Raises: + ## Raises + HTTP 422 Unprocessable Entity: If API is unable to parse env data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to create env in mongoDB or valid parameter type/structure provided but content is breaking the API. """ return await EnvController(env).create_env() @@ -228,34 +270,44 @@ async def create_env(env: Env) -> "EnvCreated": @app.get("/environments/{env_id}", tags=["ENVIRONMENT"]) async def read_env(env_id: int) -> "Env": """ - Reads an environment. + Reads an environment + + ## Args - Args: - env_id: Environment ID hash. + ``` env_id: Environment ID hash ``` - Returns: - Pydantic env object as JSON. + ## Returns - Raises: - HTTP 404 Not Found: If env_id does not exist in database. + ``` Env object as JSON. ``` + + ## Raises + + HTTP 404 Not Found: If env_id does not exist at database. """ return await EnvController.get_env(env_id) @app.put("/environments/{env_id}", tags=["ENVIRONMENT"]) async def update_env(env_id: int, env: Env) -> "EnvUpdated": """ - Updates an environment. + Updates an environment + + ## Args - Args: - env_id: Environment ID hash. - env: Pydantic env object as JSON request according to API docs. + ``` + env_id: Environment ID hash + env: Env object as JSON + ``` + + ## Returns - Returns: HTTP 200 { "message": "Environment updated successfully.", new_env_id: new_env_id_hash } - Raises: - HTTP 404 Not Found: If env_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If env_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse env data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update env in mongoDB or valid parameter type/structure provided but content is breaking the API. """ return await EnvController(env).update_env(env_id) @@ -263,253 +315,315 @@ async def update_env(env_id: int, env: Env) -> "EnvUpdated": @app.delete("/environments/{env_id}", tags=["ENVIRONMENT"]) async def delete_env(env_id: int) -> "EnvDeleted": """ - Deletes an environment. + Deletes an environment + + ## Args + + ``` env_id: Environment ID hash ``` - Args: - env_id: Environment ID hash. + ## Returns - Returns: HTTP 200 { "message": "Environment deleted successfully." } - Raises: - HTTP 404 Not Found: If env_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If env_id does not exist at database. """ return await EnvController.delete_env(env_id) @app.get("/environments/rocketpy/{env_id}", tags=["ENVIRONMENT"]) async def read_rocketpy_env(env_id: int) -> "EnvPickle": """ - Reads a rocketpy environment. + Reads a rocketpy environment + + ## Args + + ``` env_id: Environment ID hash ``` - Args: - env_id: Environment ID hash. + ## Returns - Returns: - Rocketpy Environment object encoded as JSONPickle string. + ``` Rocketpy Environment object as JSONPickle string. ``` - Raises: - HTTP 404 Not Found: If env_id does not exist in database. + ## Raises + HTTP 404 Not Found: If env_id does not exist at database. """ return await EnvController.get_rocketpy_env(env_id) @app.get("/environments/{env_id}/simulate", tags=["ENVIRONMENT"]) async def simulate_env(env_id: int) -> "EnvSummary": """ - Simulates an environment. + Simulates an environment + + ## Args + + ``` env_id: Env ID hash ``` + + ## Returns - Args: - env_id: Env ID hash. + ``` Env summary object containig simulation numbers and plots as JSON ``` - Returns: - HTTP 200 pydantic env summary object containig simulation numbers and plots as JSON. + ## Raises - Raises: - HTTP 404 Not Found: If env_id does not exist in database. + HTTP 404 Not Found: If env_id does not exist at database. """ return await EnvController.simulate(env_id) # Motor routes @app.post("/motors/", tags=["MOTOR"]) -async def create_motor(motor: Motor) -> "MotorCreated": +async def create_motor(motor: Motor, motor_kind: MotorKinds) -> "MotorCreated": """ - Creates a new motor. + Creates a new motor - Args: - Pydantic motor object as a JSON request according to API docs. + ## Args + + ``` Motor object as a JSON ``` - Returns: + ## Returns + HTTP 200 { "message": "Motor created successfully.", id: motor_id_hash } - Raises: + ## Raises + HTTP 422 Unprocessable Entity: If API is unable to parse motor data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to create motor in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await MotorController(motor).create_motor() + return await MotorController(motor, motor_kind).create_motor() @app.get("/motors/{motor_id}", tags=["MOTOR"]) async def read_motor(motor_id: int) -> "Motor": """ - Reads a motor. + Reads a motor + + ## Args + + ``` motor_id: Motor ID hash ``` - Args: - motor_id: Motor ID hash. + ## Returns - Returns: - Pydantic motor object as JSON. + Motor object as JSON. - Raises: - HTTP 404 Not Found: If motor_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If motor_id does not exist at database. """ return await MotorController.get_motor(motor_id) @app.put("/motors/{motor_id}", tags=["MOTOR"]) -async def update_motor(motor_id: int, motor: Motor) -> "MotorUpdated": +async def update_motor(motor_id: int, motor: Motor, motor_kind: MotorKinds) -> "MotorUpdated": """ - Updates a motor. + Updates a motor + + ## Args + + ``` + motor_id: Motor ID hash + motor: Motor object as JSON + ``` - Args: - motor_id: Motor ID hash. - motor: Pydantic motor object as JSON request according to API docs. + ## Returns - Returns: HTTP 200 { "message": "Motor updated successfully.", new_motor_id: new_motor_id_hash } - Raises: - HTTP 404 Not Found: If motor_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If motor_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse motor data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update motor in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await MotorController(motor).update_motor(motor_id) + return await MotorController(motor, motor_kind).update_motor(motor_id) @app.delete("/motors/{motor_id}", tags=["MOTOR"]) async def delete_motor(motor_id: int) -> "MotorDeleted": """ - Deletes a motor. + Deletes a motor - Args: - motor_id: Motor ID hash. + ## Args + + ``` motor_id: Motor ID hash ``` + + ## Returns - Returns: HTTP 200 { "message": "Motor deleted successfully." } - Raises: - HTTP 404 Not Found: If motor_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If motor_id does not exist at database. """ return await MotorController.delete_motor(motor_id) @app.get("/motors/rocketpy/{motor_id}", tags=["MOTOR"]) async def read_rocketpy_motor(motor_id: int) -> "MotorPickle": """ - Reads a rocketpy motor. + Reads a rocketpy motor - Args: - motor_id: Motor ID hash. + ## Args - Returns: - Rocketpy Motor object encoded as JSONPickle string. + ``` motor_id: Motor ID hash ``` - Raises: - HTTP 404 Not Found: If motor_id does not exist in database. + ## Returns + + ``` Rocketpy Motor object as JSONPickle string ``` + + ## Raises + HTTP 404 Not Found: If motor_id does not exist at database. """ return await MotorController.get_rocketpy_motor(motor_id) @app.get("/motors/{motor_id}/simulate", tags=["MOTOR"]) async def simulate_motor(motor_id: int) -> "MotorSummary": """ - Simulates a motor. + Simulates a motor + + ## Args - Args: - motor_id: Motor ID hash. + ``` motor_id: Motor ID hash ``` - Returns: - HTTP 200 pydantic motor summary object containig simulation numbers and plots as JSON. + ## Returns - Raises: - HTTP 404 Not Found: If motor_id does not exist in database. + ``` Motor summary as JSON ``` + + ## Raises + + HTTP 404 Not Found: If motor_id does not exist at database. """ return await MotorController.simulate(motor_id) # Rocket routes @app.post("/rockets/", tags=["ROCKET"]) -async def create_rocket(rocket: Rocket) -> "RocketCreated": +async def create_rocket(rocket: Rocket, rocket_option: RocketOptions, motor_kind: MotorKinds) -> "RocketCreated": """ - Creates a new rocket. + Creates a new rocket + + ## Args - Args: - Pydantic rocket object as a JSON request according to API docs. + ``` Rocket object as a JSON ``` - Returns: + ## Returns + HTTP 200 { "message": "Rocket created successfully.", id: rocket_id_hash } - Raises: + ## Raises + HTTP 422 Unprocessable Entity: If API is unable to parse rocket data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to create rocket in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await RocketController(rocket).create_rocket() + return await RocketController(rocket, rocket_option, motor_kind).create_rocket() @app.get("/rockets/{rocket_id}", tags=["ROCKET"]) async def read_rocket(rocket_id: int) -> Rocket: """ - Reads a rocket. + Reads a rocket - Args: - rocket_id: Rocket ID hash. + ## Args - Returns: - Pydantic rocket object as JSON. + ``` rocket_id: Rocket ID hash ``` - Raises: - HTTP 404 Not Found: If rocket_id does not exist in database. + ## Returns + + ``` Rocket object as JSON ``` + + ## Raises + + HTTP 404 Not Found: If rocket_id does not exist at database. """ return await RocketController.get_rocket(rocket_id) @app.put("/rockets/{rocket_id}", tags=["ROCKET"]) -async def update_rocket(rocket_id: int, rocket: Rocket) -> "RocketUpdated": +async def update_rocket(rocket_id: int, rocket: Rocket, rocket_option: RocketOptions, motor_kind: MotorKinds) -> "RocketUpdated": """ - Updates a rocket. + Updates a rocket - Args: - rocket_id: Rocket ID hash. - rocket: Pydantic rocket object as JSON request according to API docs. + ## Args + + ``` + rocket_id: Rocket ID hash + rocket: Rocket object as JSON + ``` + + ## Returns - Returns: HTTP 200 { "message": "Rocket updated successfully.", new_rocket_id: new_rocket_id_hash } - Raises: - HTTP 404 Not Found: If rocket_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If rocket_id does not exist at database. + HTTP 422 Unprocessable Entity: If API is unable to parse rocket data, usually happens when some parameter is invalid, please attend to API docs request specifications. + HTTP 500 Internal Server Error: If API is either unable to update rocket in mongoDB or valid parameter type/structure provided but content is breaking the API. """ - return await RocketController(rocket).update_rocket(rocket_id) + return await RocketController(rocket, rocket_option, motor_kind).update_rocket(rocket_id) @app.delete("/rockets/{rocket_id}", tags=["ROCKET"]) async def delete_rocket(rocket_id: int) -> "RocketDeleted": """ - Deletes a rocket. + Deletes a rocket + + ## Args - Args: - rocket_id: Rocket ID hash. + ``` rocket_id: Rocket ID hash ``` + + ## Returns - Returns: HTTP 200 { "message": "Rocket deleted successfully." } - Raises: - HTTP 404 Not Found: If rocket_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If rocket_id does not exist at database. """ return await RocketController.delete_rocket(rocket_id) @app.get("/rockets/rocketpy/{rocket_id}", tags=["ROCKET"]) async def read_rocketpy_rocket(rocket_id: int) -> "RocketPickle": """ - Reads a rocketpy rocket. + Reads a rocketpy rocket + + ## Args + + ``` rocket_id: Rocket ID hash ``` - Args: - rocket_id: Rocket ID hash. + ## Returns - Returns: - Rocketpy Rocket object encoded as JSONPickle string. + ``` Rocketpy Rocket object as JSONPickle string ``` - Raises: - HTTP 404 Not Found: If rocket_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If rocket_id does not exist at database. """ return await RocketController.get_rocketpy_rocket(rocket_id) @app.get("/rockets/{rocket_id}/simulate", tags=["ROCKET"]) async def simulate_rocket(rocket_id: int) -> "RocketSummary": """ - Simulates a rocket. + Simulates a rocket + + ## Args + + ``` rocket_id: Rocket ID hash ``` - Args: - rocket_id: Rocket ID hash. + ## Returns - Returns: HTTP 200 pydantic rocket summary object containig simulation numbers and plots as JSON. - Raises: - HTTP 404 Not Found: If rocket_id does not exist in database. + ## Raises + + HTTP 404 Not Found: If rocket_id does not exist at database. """ return await RocketController.simulate(rocket_id) +# Additional routes @app.get("/health", status_code=status.HTTP_200_OK, include_in_schema=False) async def __perform_healthcheck(): return {'health': 'Everything OK!'} + +# Errors +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + exc_str = f'{exc}'.replace('\n', ' ').replace(' ', ' ') + logging.error(f"{request}: {exc_str}") + content = {'status_code': 10422, 'message': exc_str, 'data': None} + return JSONResponse(content=content, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 2786d8c..5c83a5d 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -5,10 +5,15 @@ import jsonpickle -from lib.models.rocket import Rocket +from lib.models.rocket import Rocket, RocketOptions +from lib.models.motor import Motor, MotorEngines, MotorKinds from lib.models.flight import Flight from lib.models.environment import Env -from lib.views.flight import FlightSummary, SurfaceWindConditions, OutOfRailConditions, BurnoutConditions, ApogeeConditions, MaximumValues, InitialConditions, NumericalIntegrationSettings, ImpactConditions, EventsRegistered, LaunchRailConditions, FlightData, FlightCreated, FlightUpdated, FlightDeleted, FlightPickle +from lib.views.flight import ( + FlightSummary, SurfaceWindConditions, OutOfRailConditions, BurnoutConditions, ApogeeConditions, MaximumValues, + InitialConditions, NumericalIntegrationSettings, ImpactConditions, EventsRegistered, LaunchRailConditions, FlightData, + FlightCreated, FlightUpdated, FlightDeleted, FlightPickle +) from lib.repositories.flight import FlightRepository from lib.controllers.environment import EnvController from lib.controllers.rocket import RocketController @@ -31,17 +36,20 @@ class FlightController(): - Read a RocketpyFlight object from the database. """ - def __init__(self, flight: Flight): - rocketpy_rocket = RocketController(flight.rocket).rocketpy_rocket + def __init__(self, flight: Flight, rocket_option: RocketOptions, motor_kind: MotorKinds): + rocketpy_rocket = RocketController(flight.rocket, rocket_option=rocket_option, motor_kind=motor_kind).rocketpy_rocket rocketpy_env = EnvController(flight.environment).rocketpy_env - rocketpy_flight=RocketpyFlight( - rocket=rocketpy_rocket, - inclination=flight.inclination, - heading=flight.heading, - environment=rocketpy_env, - rail_length=flight.rail_length, + rocketpy_flight = RocketpyFlight( + rocket=rocketpy_rocket, + inclination=flight.inclination, + heading=flight.heading, + environment=rocketpy_env, + rail_length=flight.rail_length ) + + self.rocket_option = rocket_option + self.motor_kind = motor_kind self.rocketpy_flight = rocketpy_flight self.flight = flight @@ -53,7 +61,12 @@ async def create_flight(self) -> "Union[FlightCreated, Response]": Dict[str, str]: Flight id. """ flight = FlightRepository(flight=self.flight) - successfully_created_flight = await flight.create_flight() + successfully_created_flight = \ + await flight.create_flight( + motor_kind = self.motor_kind, + rocket_option = self.rocket_option + ) + if successfully_created_flight: return FlightCreated(flight_id=str(flight.flight_id)) return Response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -98,7 +111,9 @@ async def get_rocketpy_flight(flight_id: int) -> "Union[FlightPickle, Response]" return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_read_rocketpy_flight = \ - FlightController(successfully_read_flight).rocketpy_flight + FlightController(flight = successfully_read_flight, + rocket_option = RocketOptions(successfully_read_flight.rocket._rocket_option), + motor_kind = MotorKinds(successfully_read_flight.rocket.motor._motor_kind)).rocketpy_flight return FlightPickle(jsonpickle_rocketpy_flight=jsonpickle.encode(successfully_read_rocketpy_flight)) @@ -121,7 +136,10 @@ async def update_flight(self, flight_id: int) -> "Union[FlightUpdated, Response] return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_updated_flight = \ - await FlightRepository(flight=self.flight, flight_id=flight_id).update_flight() + await FlightRepository(flight=self.flight, flight_id=flight_id).update_flight( + rocket_option = self.rocket_option, + motor_kind = self.motor_kind + ) if successfully_updated_flight: return FlightUpdated(new_flight_id=str(successfully_updated_flight)) @@ -158,7 +176,7 @@ async def update_env(flight_id: int, env: Env) -> "Union[FlightUpdated, Response return Response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) @staticmethod - async def update_rocket(flight_id: int, rocket: Rocket) -> "Union[FlightUpdated, Response]": + async def update_rocket(flight_id: int, rocket: Rocket, rocket_option, motor_kind) -> "Union[FlightUpdated, Response]": """ Update the rocket of a flight in the database. @@ -178,7 +196,11 @@ async def update_rocket(flight_id: int, rocket: Rocket) -> "Union[FlightUpdated, return Response(status_code=status.HTTP_404_NOT_FOUND) flight = successfully_read_flight.dict() - flight["rocket"] = rocket + updated_rocket = rocket.dict() + updated_rocket["rocket_option"] = rocket_option + updated_rocket["motor"]["motor_kind"] = motor_kind + flight["rocket"] = Rocket(**updated_rocket) + flight = Flight(**flight) successfully_updated_flight = \ await FlightRepository(flight=flight, flight_id=flight_id).update_flight() @@ -231,7 +253,9 @@ async def simulate(flight_id: int) -> "Union[FlightSummary, Response]": if not successfully_read_flight: return Response(status_code=status.HTTP_404_NOT_FOUND) - flight = FlightController(successfully_read_flight).rocketpy_flight + flight = FlightController(flight = successfully_read_flight, + rocket_option = RocketOptions(successfully_read_flight.rocket._rocket_option), + motor_kind = MotorKinds(successfully_read_flight.rocket.motor._motor_kind)).rocketpy_flight _initial_conditions = InitialConditions( initial_altitude = "Attitude - e0: {:.3f} | e1: {:.3f} | e2: {:.3f} | e3: {:.3f}".format(flight.e0(0), flight.e1(0), flight.e2(0), flight.e3(0)), diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 0219713..b8afae4 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -1,9 +1,12 @@ from fastapi import Response, status from typing import Any, Dict, Union from rocketpy.motors.solid_motor import SolidMotor +from rocketpy.motors.liquid_motor import LiquidMotor +from rocketpy.motors.hybrid_motor import HybridMotor +from rocketpy import TankGeometry, Fluid import jsonpickle -from lib.models.motor import Motor +from lib.models.motor import Motor, MotorKinds from lib.repositories.motor import MotorRepository from lib.views.motor import MotorSummary, MotorData, MotorCreated, MotorUpdated, MotorDeleted, MotorPickle @@ -17,26 +20,55 @@ class MotorController(): Enables: - Create a rocketpy.Motor object from a Motor model object. """ - def __init__(self, motor: Motor): - rocketpy_motor = SolidMotor( - burn_time=motor.burn_time, - grain_number=motor.grain_number, - grain_density=motor.grain_density, - grain_outer_radius=motor.grain_outer_radius, - grain_initial_inner_radius=motor.grain_initial_inner_radius, - grain_initial_height=motor.grain_initial_height, - grains_center_of_mass_position=-motor.grains_center_of_mass_position, - thrust_source=f"lib/data/motors/{motor.thrust_source.value}.eng", - grain_separation=motor.grain_separation, - nozzle_radius=motor.nozzle_radius, - dry_mass=motor.dry_mass, - center_of_dry_mass_position=motor.center_of_dry_mass_position, - dry_inertia=motor.dry_inertia, - throat_radius=motor.throat_radius, - interpolation_method=motor.interpolation_method - ) + def __init__(self, motor: Motor, motor_kind): + self.guard(motor, motor_kind) + motor_core = { + "thrust_source": f"lib/data/motors/{motor.thrust_source.value}.eng", + "burn_time": motor.burn_time, + "nozzle_radius": motor.nozzle_radius, + "dry_mass": motor.dry_mass, + "dry_inertia": motor.dry_inertia, + "center_of_dry_mass_position": motor.center_of_dry_mass_position + } + + match motor_kind: + case MotorKinds.liquid: + rocketpy_motor = LiquidMotor(**motor_core) + case MotorKinds.hybrid: + rocketpy_motor = HybridMotor(**motor_core, + throat_radius=motor.throat_radius, + grain_number=motor.grain_number, + grain_density=motor.grain_density, + grain_outer_radius=motor.grain_outer_radius, + grain_initial_inner_radius=motor.grain_initial_inner_radius, + grain_initial_height=motor.grain_initial_height, + grain_separation=motor.grain_separation, + grains_center_of_mass_position=motor.grains_center_of_mass_position + ) + case _: + rocketpy_motor = SolidMotor(**motor_core, + grain_number=motor.grain_number, + grain_density=motor.grain_density, + grain_outer_radius=motor.grain_outer_radius, + grain_initial_inner_radius=motor.grain_initial_inner_radius, + grain_initial_height=motor.grain_initial_height, + grains_center_of_mass_position=motor.grains_center_of_mass_position, + grain_separation=motor.grain_separation, + throat_radius=motor.throat_radius, + interpolation_method=motor.interpolation_method + ) + + if motor_kind != MotorKinds.solid: + for tank in motor.tanks: + rocketpy_motor.add_tank(tank.tank, tank.position) + + self.motor_kind = motor_kind #tracks motor kind state self.rocketpy_motor = rocketpy_motor self.motor = motor + + def guard(self, motor: Motor, motor_kind): + if motor_kind != MotorKinds.solid and motor.tanks is None: + return Response(status_code=status.HTTP_400_BAD_REQUEST, content="Tanks must be provided for liquid and hybrid motors.") async def create_motor(self) -> "Union[MotorCreated, Response]": """ @@ -46,7 +78,7 @@ async def create_motor(self) -> "Union[MotorCreated, Response]": Dict[str, str]: motor id. """ motor = MotorRepository(motor=self.motor) - successfully_created_motor = await motor.create_motor() + successfully_created_motor = await motor.create_motor(motor_kind = self.motor_kind) if successfully_created_motor: return MotorCreated(motor_id=str(motor.motor_id)) return Response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -91,7 +123,8 @@ async def get_rocketpy_motor(motor_id: int) -> "Union[MotorPickle, Response]": return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_read_rocketpy_motor = \ - MotorController( successfully_read_motor ).rocketpy_motor + MotorController(motor = successfully_read_motor, + motor_kind = MotorKinds(successfully_read_motor._motor_kind)).rocketpy_motor return MotorPickle(jsonpickle_rocketpy_motor=jsonpickle.encode(successfully_read_rocketpy_motor)) @@ -114,7 +147,7 @@ async def update_motor(self, motor_id: int) -> "Union[MotorUpdated, Response]": return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_updated_motor = \ - await MotorRepository(motor=self.motor, motor_id=motor_id).update_motor() + await MotorRepository(motor=self.motor, motor_id=motor_id).update_motor(motor_kind = self.motor_kind) if successfully_updated_motor: return MotorUpdated(new_motor_id=str(successfully_updated_motor)) @@ -164,7 +197,8 @@ async def simulate(motor_id: int) -> "Union[MotorSummary, Response]": if not successfully_read_motor: return Response(status_code=status.HTTP_404_NOT_FOUND) - motor = MotorController(successfully_read_motor).rocketpy_motor + motor = MotorController(motor=successfully_read_motor, + motor_kind = MotorKinds(successfully_read_motor._motor_kind)).rocketpy_motor motor_simulation_numbers = MotorData( total_burning_time="Total Burning Time: " + str(motor.burn_out_time) + " s", diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index 29f870d..04b502f 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -12,7 +12,8 @@ from rocketpy.rocket.aero_surface import Tail as RocketpyTail from lib.controllers.motor import MotorController -from lib.models.rocket import Rocket +from lib.models.rocket import Rocket, RocketOptions +from lib.models.motor import MotorKinds from lib.models.aerosurfaces import NoseCone, TrapezoidalFins, Tail from lib.models.parachute import Parachute from lib.repositories.rocket import RocketRepository @@ -28,13 +29,13 @@ class RocketController(): Enables: create a RocketpyRocket object from a Rocket model object. """ - def __init__(self, rocket: Rocket): + def __init__(self, rocket: Rocket, rocket_option, motor_kind): rocketpy_rocket = RocketpyRocket( radius=rocket.radius, mass=rocket.mass, inertia=rocket.inertia, - power_off_drag=f"lib/data/{rocket.power_off_drag.value}/powerOffDragCurve.csv", - power_on_drag=f"lib/data/{rocket.power_on_drag.value}/powerOnDragCurve.csv", + power_off_drag=f"lib/data/{rocket_option.value.lower()}/powerOffDragCurve.csv", + power_on_drag=f"lib/data/{rocket_option.value.lower()}/powerOnDragCurve.csv", center_of_mass_without_motor=rocket.center_of_mass_without_motor, coordinate_system_orientation=rocket.coordinate_system_orientation ) @@ -43,7 +44,7 @@ def __init__(self, rocket: Rocket): rocketpy_rocket.set_rail_buttons(upper_button_position=rocket.rail_buttons.upper_button_position, lower_button_position=rocket.rail_buttons.lower_button_position, angular_position=rocket.rail_buttons.angular_position) - rocketpy_rocket.add_motor(MotorController(rocket.motor).rocketpy_motor, + rocketpy_rocket.add_motor(MotorController(rocket.motor, motor_kind).rocketpy_motor, rocket.motor_position) #NoseCone @@ -73,6 +74,7 @@ def __init__(self, rocket: Rocket): print("Parachute trigger not valid. Skipping parachute.") continue + self.rocket_option = rocket_option #tracks rocket option state self.rocketpy_rocket = rocketpy_rocket self.rocket = rocket @@ -231,7 +233,7 @@ async def create_rocket(self) -> "Union[RocketCreated, Response]": Dict[str, str]: Rocket id. """ rocket = RocketRepository(rocket=self.rocket) - successfully_created_rocket = await rocket.create_rocket() + successfully_created_rocket = await rocket.create_rocket(rocket_option = self.rocket_option) if successfully_created_rocket: return RocketCreated(rocket_id=str(rocket.rocket_id)) return Response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -276,7 +278,9 @@ async def get_rocketpy_rocket(rocket_id: int) -> "Union[RocketPickle, Response]" return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_read_rocketpy_rocket = \ - RocketController( successfully_read_rocket ).rocketpy_rocket + RocketController(rocket=successfully_read_rocket, + rocket_option = RocketOptions(successfully_read_rocket._rocket_option), + motor_kind = MotorKinds(successfully_read_rocket.motor._motor_kind)).rocketpy_rocket return RocketPickle(jsonpickle_rocketpy_rocket=jsonpickle.encode(successfully_read_rocketpy_rocket)) @@ -299,7 +303,7 @@ async def update_rocket(self, rocket_id: int) -> "Union[RocketUpdated, Response] return Response(status_code=status.HTTP_404_NOT_FOUND) successfully_updated_rocket = \ - await RocketRepository(rocket=self.rocket, rocket_id=rocket_id).update_rocket() + await RocketRepository(rocket=self.rocket, rocket_id=rocket_id).update_rocket(rocket_option = self.rocket_option) if successfully_updated_rocket: return RocketUpdated(new_rocket_id=str(successfully_updated_rocket)) @@ -349,7 +353,9 @@ async def simulate(rocket_id: int) -> "Union[RocketSummary, Response]": if not successfully_read_rocket: return Response(status_code=status.HTTP_404_NOT_FOUND) - rocket = RocketController(successfully_read_rocket).rocketpy_rocket + rocket = RocketController(rocket=successfully_read_rocket, + rocket_option = RocketOptions(successfully_read_rocket._rocket_option), + motor_kind = MotorKinds(successfully_read_rocket.motor._motor_kind)).rocketpy_rocket _inertia_details = InertiaDetails( rocket_mass_without_propellant = "Rocket Mass: {:.3f} kg (No Propellant)".format(rocket.mass), diff --git a/lib/models/environment.py b/lib/models/environment.py index ee86ece..4a1a5bc 100644 --- a/lib/models/environment.py +++ b/lib/models/environment.py @@ -6,6 +6,8 @@ class Env(BaseModel, frozen=True): latitude: float = 0 longitude: float = 0 elevation: Optional[int] = 1400 + + #Opional parameters atmospheric_model_type: Optional[str] = 'standard_atmosphere' atmospheric_model_file: Optional[str] = 'GFS' date: Optional[datetime.datetime] = datetime.datetime.today() + datetime.timedelta(days=1) diff --git a/lib/models/motor.py b/lib/models/motor.py index 84e2217..fb57453 100644 --- a/lib/models/motor.py +++ b/lib/models/motor.py @@ -1,26 +1,115 @@ -from typing import Optional, Tuple +from rocketpy import LevelBasedTank, MassBasedTank, MassFlowRateBasedTank, UllageBasedTank, SolidMotor, LiquidMotor, HybridMotor, TankGeometry, Function +from typing import Optional, Tuple, List, Dict from enum import Enum -from pydantic import BaseModel +from pydantic import BaseModel, PrivateAttr -class MotorOptions(str, Enum): +class MotorKinds(str, Enum): + hybrid: str = "Hybrid" + solid: str = "Solid" + liquid: str = "Liquid" + +class MotorEngines(str, Enum): cesaroni: str = "Cesaroni_M1670" - custom: str = "Custom (Coming soon)" + custom: str = "Custom" + +class TankKinds(str, Enum): + level: str = "Level" + mass: str = "Mass" + mass_flow: str = "MassFlow" + ullage: str = "Ullage" + +class TankFluids(BaseModel, frozen=True): + name: str = "FluidName" + density: float = 100.0 + +class MotorTank(BaseModel, frozen=True): + #Required parameters + geometry: "List[Tuple[Tuple[float,float],float]]" = [((0, 5), 1), ((5, 10), 2)] + tank_kind: TankKinds = TankKinds.mass_flow + gas: TankFluids = TankFluids() + liquid: TankFluids = TankFluids() + name: str = "Tank" + flux_time: "List[float]" = [0, 8] + position: float = 1.0 + + #Optional parameters + discretize: Optional[int] = 100 + liquid_height: Optional[float] = 0.5 + liquid_mass: Optional[float] = 5.0 + gas_mass: Optional[float] = 0.1 + gas_mass_flow_rate_in: Optional[float] = 0.1 + gas_mass_flow_rate_out: Optional[float] = 0.1 + liquid_mass_flow_rate_in: Optional[float] = 0.1 + liquid_mass_flow_rate_out: Optional[float] = 0.1 + initial_liquid_mass: Optional[float] = 5.0 + initial_gas_mass: Optional[float] = 0.1 + ullage: Optional[float] = 0.1 + + def __init__(self, **kwargs): + super().__init__(**kwargs) + tank_core = { + "name": self.name, + "geometry": TankGeometry(geometry_dict = { t:f for t,f in self.geometry }), + "flux_time": self.flux_time, + "gas": self.gas, + "liquid": self.liquid, + "discretize": self.discretize + } + + match self.tank_kind: + case TankKinds.level: + tank = LevelBasedTank(**tank_core, liquid_height=self.liquid_height) + case TankKinds.mass: + tank = MassBasedTank(**tank_core, + liquid_mass=self.liquid_mass, gas_mass=self.gas_mass) + case TankKinds.mass_flow: + tank = MassFlowRateBasedTank(**tank_core, + gas_mass_flow_rate_in=self.gas_mass_flow_rate_in, + gas_mass_flow_rate_out=self.gas_mass_flow_rate_out, + liquid_mass_flow_rate_in=self.liquid_mass_flow_rate_in, + liquid_mass_flow_rate_out=self.liquid_mass_flow_rate_out, + initial_liquid_mass=self.initial_liquid_mass, + initial_gas_mass=self.initial_gas_mass + ) + case TankKinds.ullage: + tank = UllageBasedTank(**tank_core, ullage=self.ullage) + object.__setattr__(self, "tank", tank) + + def __hash__(self): + temp = vars(self) + temp = str(temp) + return hash(temp) class Motor(BaseModel, frozen=True): + #TBD: thrust_source must be the id of a previously uploaded .eng file and a list of "default" files must be provided in the api docs + + #Required parameters + thrust_source: MotorEngines = MotorEngines.cesaroni burn_time: float = 3.9 + nozzle_radius: float = 0.033 dry_mass: float = 1.815 dry_inertia: "Tuple[float, float, float]" = (0.125, 0.125, 0.002) center_of_dry_mass_position: float = 0.317 - grain_number: int = 5 - grain_density: float = 1815 - grain_outer_radius: float = 0.033 - grain_initial_inner_radius: float = 0.015 - grain_initial_height: float = 0.12 - grains_center_of_mass_position: float = -0.85704 - #TBD: thrust_source must be the id of a previously uploaded .eng file and a list of "default" files must be provided in the api docs - thrust_source: MotorOptions = "Cesaroni_M1670" - grain_separation: float = 0.005 - nozzle_radius: float = 0.033 - throat_radius: float = 0.011 - interpolation_method: str = "linear" - coordinate_system_orientation: str = "nozzle_to_combustion_chamber" + _motor_kind: MotorKinds = PrivateAttr() + + #Optional parameters + tanks: Optional["List[MotorTank]"] = [MotorTank()] + grain_number: Optional[int] = 5 + grain_density: Optional[float] = 1815 + grain_outer_radius: Optional[float] = 0.033 + grain_initial_inner_radius: Optional[float] = 0.015 + grain_initial_height: Optional[float] = 0.12 + grains_center_of_mass_position: Optional[float] = -0.85704 + grain_separation: Optional[float] = 0.005 + throat_radius: Optional[float] = 0.011 + interpolation_method: Optional[str] = "linear" + coordinate_system_orientation: Optional[str] = "nozzle_to_combustion_chamber" + + def __init__(self, motor_kind = MotorKinds.solid, **kwargs): + super().__init__(**kwargs) + self._motor_kind = motor_kind + + def __hash__(self): + temp = vars(self) + temp = str(temp) + return hash(temp) diff --git a/lib/models/parachute.py b/lib/models/parachute.py index 8b075fa..f1106a3 100644 --- a/lib/models/parachute.py +++ b/lib/models/parachute.py @@ -4,8 +4,8 @@ class Parachute(BaseModel, frozen=True): name: "List[str]" = ["Main","Drogue"] cd_s: "List[float]" = [10, 1] - sampling_rate: "List[int]" = [105, 105] lag: "List[float]" = [1.5, 1.5] + sampling_rate: "List[int]" = [105, 105] noise: "List[Tuple[float, float, float]]" = [(0, 8.3, 0.5), (0, 8.3, 0.5)] triggers: "List[str]" = ["lambda p, h, y: y[5] < 0 and h < 800", "lambda p, h, y: y[5] < 0"] diff --git a/lib/models/rocket.py b/lib/models/rocket.py index 632b5bf..c31abda 100644 --- a/lib/models/rocket.py +++ b/lib/models/rocket.py @@ -1,28 +1,48 @@ -from typing import Optional, Tuple -from pydantic import BaseModel +from typing import Optional, Tuple, List +from pydantic import BaseModel, PrivateAttr from enum import Enum from lib.models.motor import Motor from lib.models.aerosurfaces import Fins, NoseCone, Tail, RailButtons from lib.models.parachute import Parachute class RocketOptions(str, Enum): - calisto: str = "calisto" - custom: str = "Custom (Coming soon)" + calisto: str = "Calisto" + custom: str = "Custom" class Rocket(BaseModel, frozen=True): - radius: float = 0.0632 - mass: float = 16.235 - inertia: "Tuple[float, float, float]" = (6.321, 6.321, 0.0346) - #TBD: powerOffDrag an powerOnDrag need to be the id of previously uploaded .csv files and a list of "default" files must be provided in the api docs - power_off_drag: RocketOptions = "calisto" - power_on_drag: RocketOptions = "calisto" - center_of_mass_without_motor: int = 0 - #TBD: a list of possible tailToNose values must be provided in the api docs - coordinate_system_orientation: Optional[str] = "tail_to_nose" - motor_position: float = -1.255 + #Required parameters rail_buttons: RailButtons = RailButtons() motor: Motor = Motor() nose: NoseCone = NoseCone() fins: Fins = Fins() tail: Tail = Tail() parachutes: Parachute = Parachute() + inertia: "Tuple[float, float, float]" = (6.321, 6.321, 0.0346) + center_of_mass_without_motor: int = 0 + radius: float = 0.0632 + mass: float = 16.235 + motor_position: float = -1.255 + power_off_drag: "List[Tuple[float, float]]" = [ + (0.01,0.333865758), + (0.02,0.394981721), + (0.03,0.407756063) + ] + power_on_drag: "List[Tuple[float, float]]" = [ + (0.01,0.333865758), + (0.02,0.394981721), + (0.03,0.407756063) + ] + _rocket_option: RocketOptions = PrivateAttr() + + #Optional parameters + #TBD: a list of possible tailToNose values must be provided in the api docs + coordinate_system_orientation: Optional[str] = "tail_to_nose" + + def __init__(self, rocket_option = RocketOptions.calisto, **kwargs): + super().__init__(**kwargs) + self._rocket_option = rocket_option + + def __hash__(self): + temp = vars(self) + temp = str(temp) + return hash(temp) diff --git a/lib/repositories/environment.py b/lib/repositories/environment.py index 9a8cd27..ddc9dbc 100644 --- a/lib/repositories/environment.py +++ b/lib/repositories/environment.py @@ -76,7 +76,6 @@ async def get_env(self) -> "Union[Env, None]": try: environment = await self.collection.find_one({ "env_id": self.env_id }) if environment is not None: - del environment["_id"] return Env.parse_obj(environment) return None except: diff --git a/lib/repositories/flight.py b/lib/repositories/flight.py index 3eb6da6..eea93de 100644 --- a/lib/repositories/flight.py +++ b/lib/repositories/flight.py @@ -26,7 +26,7 @@ def __init__(self, flight: Flight = None, flight_id: str = None): def __del__(self): super().__del__() - async def create_flight(self) -> "InsertOneResult": + async def create_flight(self, motor_kind: str = "Solid", rocket_option: str = "Calisto") -> "InsertOneResult": """ Creates a flight in the database @@ -40,12 +40,14 @@ async def create_flight(self) -> "InsertOneResult": try: flight_to_dict = self.flight.dict() flight_to_dict["flight_id"] = self.flight_id + flight_to_dict["rocket"]["rocket_option"] = rocket_option + flight_to_dict["rocket"]["motor"]["motor_kind"] = motor_kind return await self.collection.insert_one(flight_to_dict) except: raise Exception("Error creating flight") return InsertOneResult( acknowledged=True, inserted_id=None ) - async def update_flight(self) -> "Union[int, None]": + async def update_flight(self, motor_kind: str = "Solid", rocket_option: str = "Calisto") -> "Union[int, None]": """ Updates a flight in the database @@ -55,6 +57,8 @@ async def update_flight(self) -> "Union[int, None]": try: flight_to_dict = self.flight.dict() flight_to_dict["flight_id"] = self.flight.__hash__() + flight_to_dict["rocket"]["rocket_option"] = rocket_option + flight_to_dict["rocket"]["motor"]["motor_kind"] = motor_kind updated_flight = await self.collection.update_one( { "flight_id": self.flight_id }, @@ -76,7 +80,6 @@ async def get_flight(self) -> "Union[Flight, None]": try: flight = await self.collection.find_one({ "flight_id": self.flight_id }) if flight is not None: - del flight["_id"] return Flight.parse_obj(flight) return None except: diff --git a/lib/repositories/motor.py b/lib/repositories/motor.py index 29410de..030bf3e 100644 --- a/lib/repositories/motor.py +++ b/lib/repositories/motor.py @@ -15,7 +15,7 @@ class MotorRepository(Repository): Enables CRUD operations on motor objects """ - def __init__(self, motor: Motor = None, motor_id: str = None): + def __init__(self, motor: Motor = None, motor_id: str = None) -> None: super().__init__("motors") self.motor = motor if motor_id: @@ -26,7 +26,7 @@ def __init__(self, motor: Motor = None, motor_id: str = None): def __del__(self): super().__del__() - async def create_motor(self) -> "InsertOneResult": + async def create_motor(self, motor_kind: str = "solid") -> "InsertOneResult": """ Creates a motor in the database @@ -40,12 +40,13 @@ async def create_motor(self) -> "InsertOneResult": try: motor_to_dict = self.motor.dict() motor_to_dict["motor_id"] = self.motor_id + motor_to_dict["motor_kind"] = motor_kind return await self.collection.insert_one(motor_to_dict) except: raise Exception("Error creating motor") return InsertOneResult( acknowledged=True, inserted_id=None ) - async def update_motor(self) -> "Union[int, None]": + async def update_motor(self, motor_kind: str = "solid") -> "Union[int, None]": """ Updates a motor in the database @@ -55,6 +56,7 @@ async def update_motor(self) -> "Union[int, None]": try: motor_to_dict = self.motor.dict() motor_to_dict["motor_id"] = self.motor.__hash__() + motor_to_dict["motor_kind"] = motor_kind updated_motor = await self.collection.update_one( { "motor_id": self.motor_id }, @@ -76,7 +78,6 @@ async def get_motor(self) -> "Union[motor, None]": try: motor = await self.collection.find_one({ "motor_id": self.motor_id }) if motor is not None: - del motor["_id"] return Motor.parse_obj(motor) return None except: diff --git a/lib/repositories/repo_sync.py b/lib/repositories/repo_sync.py deleted file mode 100644 index 6117924..0000000 --- a/lib/repositories/repo_sync.py +++ /dev/null @@ -1,22 +0,0 @@ -from pymongo import MongoClient -from lib.secrets import secrets_instance - -class Repository: - """ - Base class for all repositories (singleton) - """ - _self = None - - def __new__(cls, *args, **kwargs): - if cls._self is None: - cls._self = super().__new__(cls) - return cls._self - - def __init__(self, collection: str): - self.connection_string = secrets_instance.get_secret("MONGODB_CONNECTION_STRING") - self.client = MongoClient(self.connection_string) - self.db = self.client.rocketpy - self.collection = self.db[collection] - - def __del__(self): - self.client.close() diff --git a/lib/repositories/rocket.py b/lib/repositories/rocket.py index 0770948..ab03807 100644 --- a/lib/repositories/rocket.py +++ b/lib/repositories/rocket.py @@ -26,7 +26,7 @@ def __init__(self, rocket: Rocket = None, rocket_id: str = None): def __del__(self): super().__del__() - async def create_rocket(self) -> "InsertOneResult": + async def create_rocket(self, rocket_option: str = "Calisto") -> "InsertOneResult": """ Creates a rocket in the database @@ -40,12 +40,13 @@ async def create_rocket(self) -> "InsertOneResult": try: rocket_to_dict = self.rocket.dict() rocket_to_dict["rocket_id"] = self.rocket_id + rocket_to_dict["rocket_option"] = rocket_option return await self.collection.insert_one(rocket_to_dict) except: raise Exception("Error creating rocket") return InsertOneResult( acknowledged=True, inserted_id=None ) - async def update_rocket(self) -> "Union[int, None]": + async def update_rocket(self, rocket_option: str = "Calisto") -> "Union[int, None]": """ Updates a rocket in the database @@ -55,6 +56,7 @@ async def update_rocket(self) -> "Union[int, None]": try: rocket_to_dict = self.rocket.dict() rocket_to_dict["rocket_id"] = self.rocket.__hash__() + rocket_to_dict["rocket_option"] = rocket_option updated_rocket = await self.collection.update_one( { "rocket_id": self.rocket_id }, @@ -76,7 +78,6 @@ async def get_rocket(self) -> "Union[Rocket, None]": try: rocket = await self.collection.find_one({ "rocket_id": self.rocket_id }) if rocket is not None: - del rocket["_id"] return Rocket.parse_obj(rocket) return None except: diff --git a/lib/secrets.py b/lib/secrets.py index 9565b7b..e719203 100644 --- a/lib/secrets.py +++ b/lib/secrets.py @@ -15,7 +15,6 @@ def get_os_secret(key): def get_secret(self, key): dotenv_secret = self.secrets.get(key) if not dotenv_secret: - print("cheguei") return self.get_os_secret(key) return dotenv_secret