From 2821c8b94cb3d99cd748ae3dae07198345ae8b6d Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Thu, 13 Jun 2024 22:05:51 -0300 Subject: [PATCH 1/9] increase version extends opentelemetry instrumentation properly threat mongo db errors refactors environment summary serialization Minor refactoring - Enhances error handling and logging - Fine tune hybrid locks on main repository initialization and finalization refactor repositories accesses fine tunes db config turn off db connection close fixes repo name fixes get environment call on flight route --- Dockerfile | 2 +- lib/__init__.py | 3 + lib/api.py | 8 +- lib/controllers/environment.py | 83 +++++++++-------- lib/controllers/flight.py | 54 ++++++++++- lib/controllers/motor.py | 35 +++++++ lib/controllers/rocket.py | 35 +++++++ lib/repositories/environment.py | 38 ++++---- lib/repositories/flight.py | 38 ++++---- lib/repositories/motor.py | 38 ++++---- lib/repositories/repo.py | 156 +++++++++++++++++++++----------- lib/repositories/rocket.py | 38 ++++---- lib/services/environment.py | 39 ++++++++ lib/settings/gunicorn.py | 2 +- lib/views/environment.py | 74 ++++++++------- pyproject.toml | 3 +- requirements.txt | 2 + 17 files changed, 453 insertions(+), 195 deletions(-) create mode 100644 lib/services/environment.py diff --git a/Dockerfile b/Dockerfile index 573f83f..831a57b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN apt-get update && \ COPY ./lib /app/lib -CMD ["gunicorn", "-c", "lib/settings/gunicorn.py", "-w", "1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib.api:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "30"] +CMD ["gunicorn", "-c", "lib/settings/gunicorn.py", "-w", "1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib.api:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "35"] diff --git a/lib/__init__.py b/lib/__init__.py index 2f27218..0dbaa09 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -23,3 +23,6 @@ def parse_error(error): exc_type = type(error).__name__ exc_obj = f"{error}".replace("\n", " ").replace(" ", " ") return f"{exc_type} exception: {exc_obj}" + + +from lib.api import app diff --git a/lib/api.py b/lib/api.py index b6af499..861cb3f 100644 --- a/lib/api.py +++ b/lib/api.py @@ -9,6 +9,9 @@ from fastapi.openapi.utils import get_openapi from fastapi.responses import RedirectResponse, JSONResponse +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from opentelemetry.instrumentation.requests import RequestsInstrumentor + from lib import logger, parse_error from lib.routes import flight, environment, motor, rocket @@ -30,6 +33,9 @@ app.include_router(motor.router) app.include_router(rocket.router) +FastAPIInstrumentor.instrument_app(app) +RequestsInstrumentor().instrument() + # Compress responses above 1KB app.add_middleware(GZipMiddleware, minimum_size=1000) @@ -39,7 +45,7 @@ def custom_openapi(): return app.openapi_schema openapi_schema = get_openapi( title="RocketPy Infinity-API", - version="1.2.0 BETA", + version="1.2.2 BETA", description=( "

RocketPy Infinity-API is a RESTful Open API for RocketPy, a rocket flight simulator.

" "
" diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index 628c96d..9bb71e7 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -1,16 +1,15 @@ from typing import Union import jsonpickle -from rocketpy.environment.environment import Environment as RocketPyEnvironment from fastapi import HTTPException, status +from pymongo.errors import PyMongoError from lib import logger, parse_error from lib.models.environment import Env +from lib.services.environment import EnvironmentService from lib.repositories.environment import EnvRepository from lib.views.environment import ( EnvSummary, - EnvData, - EnvPlots, EnvCreated, EnvDeleted, EnvUpdated, @@ -26,7 +25,7 @@ class EnvController: env: models.Env Enables: - - Simulation of RocketPyEnvironment from models.Env + - Simulation of a RocketPy Environment from models.Env - CRUD operations over models.Env on the database """ @@ -41,25 +40,6 @@ def env(self) -> Env: def env(self, env: Env): self._env = env - @staticmethod - def get_rocketpy_env(env: Env) -> RocketPyEnvironment: - """ - Get the rocketpy env object. - - Returns: - RocketPyEnvironment - """ - rocketpy_env = RocketPyEnvironment( - latitude=env.latitude, - longitude=env.longitude, - elevation=env.elevation, - date=env.date, - ) - rocketpy_env.set_atmospheric_model( - type=env.atmospheric_model_type, file=env.atmospheric_model_file - ) - return rocketpy_env - async def create_env(self) -> Union[EnvCreated, HTTPException]: """ Create a env in the database. @@ -71,6 +51,16 @@ async def create_env(self) -> Union[EnvCreated, HTTPException]: async with EnvRepository() as env_repo: 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}") @@ -103,6 +93,16 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: async with EnvRepository() as env_repo: 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}") @@ -141,7 +141,7 @@ async def get_rocketpy_env_as_jsonpickle( """ try: read_env = await cls.get_env_by_id(env_id) - rocketpy_env = cls.get_rocketpy_env(read_env) + rocketpy_env = EnvironmentService.from_env_model(read_env) except HTTPException as e: raise e from e except Exception as e: @@ -182,6 +182,16 @@ async def update_env_by_id( env_repo.fetch_env(self.env) 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}") @@ -215,6 +225,16 @@ async def delete_env_by_id( try: 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}") @@ -240,24 +260,15 @@ async def simulate_env( env_id: str. Returns: - views.EnvSummary + EnvSummary Raises: HTTP 404 Not Found: If the env does not exist in the database. """ try: read_env = await cls.get_env_by_id(env_id) - rocketpy_env = cls.get_rocketpy_env(read_env) - - env_simulation_numbers = EnvData.parse_obj( - rocketpy_env.all_info_returned() - ) - env_simulation_plots = EnvPlots.parse_obj( - rocketpy_env.all_plot_info_returned() - ) - env_summary = EnvSummary( - env_data=env_simulation_numbers, env_plots=env_simulation_plots - ) + rocketpy_env = EnvironmentService.from_env_model(read_env) + env_summary = rocketpy_env.get_env_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index d396595..af2b307 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -1,5 +1,6 @@ from typing import Union from fastapi import HTTPException, status +from pymongo.errors import PyMongoError from rocketpy.simulation.flight import Flight as RocketPyFlight @@ -31,6 +32,7 @@ from lib.repositories.flight import FlightRepository from lib.controllers.environment import EnvController from lib.controllers.rocket import RocketController +from lib.services.environment import EnvironmentService class FlightController: @@ -87,7 +89,7 @@ def get_rocketpy_flight(flight: Flight) -> RocketPyFlight: RocketPyFlight """ rocketpy_rocket = RocketController.get_rocketpy_rocket(flight.rocket) - rocketpy_env = EnvController.get_rocketpy_env(flight.environment) + rocketpy_env = EnvironmentService.from_env_model(flight.environment) rocketpy_flight = RocketPyFlight( rocket=rocketpy_rocket, inclination=flight.inclination, @@ -111,6 +113,14 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: motor_kind=self.motor_kind, 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}") @@ -143,6 +153,16 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: async with FlightRepository() as flight_repo: 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}") @@ -225,6 +245,14 @@ async def update_flight_by_id( rocket_option=self.rocket_option, ) 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}") @@ -268,6 +296,14 @@ async def update_env_by_flight_id( rocket_option=read_flight.rocket.rocket_option, ) 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: @@ -315,6 +351,14 @@ async def update_rocket_by_flight_id( motor_kind=motor_kind, rocket_option=rocket_option ) 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: @@ -350,6 +394,14 @@ async def delete_flight_by_id( try: 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 5ab9744..2ba8588 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -1,5 +1,6 @@ from typing import Union from fastapi import HTTPException, status +from pymongo.errors import PyMongoError from rocketpy.motors.solid_motor import SolidMotor from rocketpy.motors.liquid_motor import LiquidMotor from rocketpy.motors.hybrid_motor import HybridMotor @@ -119,6 +120,14 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: async with MotorRepository() as motor_repo: 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}") @@ -151,6 +160,16 @@ async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: async with MotorRepository() as motor_repo: 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}") @@ -230,6 +249,14 @@ async def update_motor_by_id( motor_repo.fetch_motor(self.motor) 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}") @@ -263,6 +290,14 @@ async def delete_motor_by_id( try: 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 d0719dd..90b54a3 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -4,6 +4,7 @@ import jsonpickle from fastapi import HTTPException, status +from pymongo.errors import PyMongoError # TODO # from inspect import getsourcelines @@ -159,6 +160,14 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: rocket_option=self.rocket_option, 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}") @@ -193,6 +202,16 @@ async def get_rocket_by_id( async with RocketRepository() as rocket_repo: 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}") @@ -274,6 +293,14 @@ async def update_rocket_by_id( motor_kind=self.motor_kind, ) 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}") @@ -307,6 +334,14 @@ async def delete_rocket_by_id( try: 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/environment.py b/lib/repositories/environment.py index a494d3b..351b9c6 100644 --- a/lib/repositories/environment.py +++ b/lib/repositories/environment.py @@ -1,7 +1,8 @@ from typing import Union -from lib import logger, parse_error +from pymongo.errors import PyMongoError from lib.models.environment import Env -from lib.repositories.repo import Repository +from lib import logger +from lib.repositories.repo import Repository, RepositoryNotInitializedException class EnvRepository(Repository): @@ -41,14 +42,17 @@ def env_id(self, env_id: "str"): self._env_id = env_id async def insert_env(self, env_data: dict): - await self.collection.insert_one(env_data) + collection = self.get_collection() + await collection.insert_one(env_data) return self async def find_env(self, env_id: str): - return await self.collection.find_one({"env_id": env_id}) + collection = self.get_collection() + return await collection.find_one({"env_id": env_id}) async def delete_env(self, env_id: str): - await self.collection.delete_one({"env_id": env_id}) + collection = self.get_collection() + await collection.delete_one({"env_id": env_id}) return self async def create_env(self): @@ -62,10 +66,10 @@ async def create_env(self): environment_to_dict = self.env.dict() environment_to_dict["env_id"] = self.env_id await self.insert_env(environment_to_dict) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.environment.create_env: {exc_str}") - raise Exception(f"Error creating environment: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -84,10 +88,10 @@ async def get_env_by_id(self, env_id: str) -> Union[Env, None]: read_env = await self.find_env(env_id) parsed_env = Env.parse_obj(read_env) if read_env else None self.env = parsed_env - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.environment.get_env: {exc_str}") - raise Exception(f"Error getting environment: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -104,10 +108,10 @@ async def delete_env_by_id(self, env_id: str): """ try: await self.delete_env(env_id) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.environment.delete_env: {exc_str}") - raise Exception(f"Error deleting environment: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: diff --git a/lib/repositories/flight.py b/lib/repositories/flight.py index 7aa9964..ac68549 100644 --- a/lib/repositories/flight.py +++ b/lib/repositories/flight.py @@ -1,7 +1,8 @@ from typing import Union -from lib import logger, parse_error +from pymongo.errors import PyMongoError +from lib import logger from lib.models.flight import Flight -from lib.repositories.repo import Repository +from lib.repositories.repo import Repository, RepositoryNotInitializedException class FlightRepository(Repository): @@ -41,13 +42,16 @@ def flight_id(self, flight_id: "str"): self._flight_id = flight_id async def insert_flight(self, flight_data: dict): - await self.collection.insert_one(flight_data) + collection = self.get_collection() + await collection.insert_one(flight_data) async def find_flight(self, flight_id: str): - return await self.collection.find_one({"flight_id": flight_id}) + collection = self.get_collection() + return await collection.find_one({"flight_id": flight_id}) async def delete_flight(self, flight_id: str): - await self.collection.delete_one({"flight_id": flight_id}) + collection = self.get_collection() + await collection.delete_one({"flight_id": flight_id}) return self async def create_flight( @@ -69,10 +73,10 @@ async def create_flight( flight_to_dict["rocket"]["rocket_option"] = rocket_option flight_to_dict["rocket"]["motor"]["motor_kind"] = motor_kind await self.insert_flight(flight_to_dict) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.flight.create_flight: {exc_str}") - raise Exception(f"Error creating flight: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -93,10 +97,10 @@ async def get_flight_by_id(self, flight_id: str) -> Union[Flight, None]: Flight.parse_obj(read_flight) if read_flight else None ) self.flight = parsed_flight - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.flight.get_flight: {exc_str}") - raise Exception(f"Error getting flight: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -113,10 +117,10 @@ async def delete_flight_by_id(self, flight_id: str): """ try: await self.delete_flight(flight_id) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.flight.delete_flight: {exc_str}") - raise Exception(f"Error deleting flight: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: diff --git a/lib/repositories/motor.py b/lib/repositories/motor.py index fb9d34f..806016a 100644 --- a/lib/repositories/motor.py +++ b/lib/repositories/motor.py @@ -1,7 +1,8 @@ from typing import Union -from lib import logger, parse_error +from pymongo.errors import PyMongoError +from lib import logger from lib.models.motor import Motor -from lib.repositories.repo import Repository +from lib.repositories.repo import Repository, RepositoryNotInitializedException class MotorRepository(Repository): @@ -41,14 +42,17 @@ def motor_id(self, motor_id: "str"): self._motor_id = motor_id async def insert_motor(self, motor_data: dict): - await self.collection.insert_one(motor_data) + collection = self.get_collection() + await collection.insert_one(motor_data) return self async def find_motor(self, motor_id: str): - return await self.collection.find_one({"motor_id": motor_id}) + collection = self.get_collection() + return await collection.find_one({"motor_id": motor_id}) async def delete_motor(self, motor_id: str): - await self.collection.delete_one({"motor_id": motor_id}) + collection = self.get_collection() + await collection.delete_one({"motor_id": motor_id}) return self async def create_motor(self, motor_kind: str = "SOLID"): @@ -66,10 +70,10 @@ async def create_motor(self, motor_kind: str = "SOLID"): motor_to_dict["motor_id"] = self.motor_id motor_to_dict["motor_kind"] = motor_kind await self.insert_motor(motor_to_dict) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.motor.create_motor: {exc_str}") - raise Exception(f"Error creating motor: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -88,10 +92,10 @@ async def get_motor_by_id(self, motor_id: str) -> Union[motor, None]: read_motor = await self.find_motor(motor_id) parsed_motor = Motor.parse_obj(read_motor) if read_motor else None self.motor = parsed_motor - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.motor.get_motor: {exc_str}") - raise Exception(f"Error getting motor: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -108,10 +112,10 @@ async def delete_motor_by_id(self, motor_id: str): """ try: await self.delete_motor(motor_id) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.motor.delete_motor: {exc_str}") - raise Exception(f"Error deleting motor: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: diff --git a/lib/repositories/repo.py b/lib/repositories/repo.py index 38fa949..4a56810 100644 --- a/lib/repositories/repo.py +++ b/lib/repositories/repo.py @@ -1,8 +1,37 @@ import asyncio -from motor.motor_asyncio import AsyncIOMotorClient -from pymongo.server_api import ServerApi +import threading from lib import logger from lib.secrets import Secrets +from pydantic import BaseModel +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 RepoInstances(BaseModel): + instance: object + prospecting: int = 0 + + def add_prospecting(self): + self.prospecting += 1 + + def remove_prospecting(self): + self.prospecting -= 1 + + def get_prospecting(self): + return self.prospecting + + def get_instance(self): + return self.instance class Repository: @@ -10,50 +39,79 @@ class Repository: Base class for all repositories (singleton) """ - _instances = {} - _lock = asyncio.Lock() + _global_instances = {} + _global_thread_lock = threading.RLock() + _global_async_lock = asyncio.Lock() def __new__(cls, *args, **kwargs): - if cls not in cls._instances: - instance = super(Repository, cls).__new__(cls) - cls._instances[cls] = instance - return cls._instances[cls] + with ( + cls._global_thread_lock + ): # Ensure thread safety for singleton instance creation + if cls not in cls._global_instances: + instance = super(Repository, cls).__new__(cls) + cls._global_instances[cls] = RepoInstances(instance=instance) + else: + cls._global_instances[cls].add_prospecting() + return cls._global_instances[cls].get_instance() - def __init__(self, collection_name: str): - self._entered = False + @classmethod + def _stop_prospecting(cls): + if cls in cls._global_instances: + cls._global_instances[cls].remove_prospecting() + + @classmethod + def _get_instance_prospecting(cls): + if cls in cls._global_instances: + return cls._global_instances[cls].get_prospecting() + return 0 + + def __init__(self, collection_name: str, *, max_pool_size: int = 10): if not getattr(self, '_initialized', False): + self._max_pool_size = max_pool_size self._collection_name = collection_name self._initialized_event = asyncio.Event() - if not asyncio.get_event_loop().is_running(): - asyncio.run(self._async_init()) - else: - loop = asyncio.get_event_loop() - loop.create_task(self._async_init()).add_done_callback( - self._on_init_done - ) + self._initialize() + + @retry(stop=stop_after_attempt(5), wait=wait_fixed(0.2)) + async def _async_init(self): + async with ( + self._global_async_lock + ): # Hybrid safe locks for initialization + with self._global_thread_lock: + try: + self._initialize_connection() + self._initialized = True + except Exception as e: + logger.error("Initialization failed: %s", e, exc_info=True) + self._initialized = False def _on_init_done(self, future): try: future.result() - except Exception as e: - logger.error("Initialization failed: %s", e, exc_info=True) - raise e from e - - async def _async_init(self): - async with self._lock: - self._initialize_connection() - self._initialized = True + finally: self._initialized_event.set() + def _initialize(self): + if not asyncio.get_event_loop().is_running(): + asyncio.run(self._async_init()) + else: + loop = asyncio.get_event_loop() + loop.create_task(self._async_init()).add_done_callback( + self._on_init_done + ) + + def __del__(self): + with self._global_thread_lock: + self._global_instances.pop(self.__class__, None) + self._initialized = False + self._stop_prospecting() + async def __aenter__(self): - if not self._entered: - 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._lock: - self._cleanup_instance() def _initialize_connection(self): try: @@ -63,9 +121,10 @@ def _initialize_connection(self): self._client = AsyncIOMotorClient( self._connection_string, server_api=ServerApi("1"), - maxIdleTimeMS=5000, - connectTimeoutMS=5000, - serverSelectionTimeoutMS=30000, + maxIdleTimeMS=30000, + minPoolSize=10, + maxPoolSize=self._max_pool_size, + serverSelectionTimeoutMS=60000, ) self._collection = self._client.rocketpy[self._collection_name] logger.info("MongoDB client initialized for %s", self.__class__) @@ -77,34 +136,25 @@ def _initialize_connection(self): "Could not establish a connection with MongoDB." ) from e - def _cleanup_instance(self): - if hasattr(self, '_client'): - self.client.close() - logger.info("Connection closed for %s", self.__class__) - self._instances.pop(self.__class__, None) + def _get_connection_string(self): + if not getattr(self, '_initialized', False): + raise RepositoryNotInitializedException() + return self._connection_string @property def connection_string(self): - return self._connection_string - - @connection_string.setter - def connection_string(self, value): - self._connection_string = value + return self._get_connection_string() - @property - def client(self): + def _get_client(self): if not getattr(self, '_initialized', False): - raise RuntimeError("Repository not initialized yet") + raise RepositoryNotInitializedException() return self._client - @client.setter - def client(self, value): - if not getattr(self, '_initialized', False): - raise RuntimeError("Repository not initialized yet") - self._client = value - @property - def collection(self): + def client(self): + return self._get_client() + + def get_collection(self): if not getattr(self, '_initialized', False): - raise RuntimeError("Repository not initialized yet") + raise RepositoryNotInitializedException() return self._collection diff --git a/lib/repositories/rocket.py b/lib/repositories/rocket.py index d272369..2c74cee 100644 --- a/lib/repositories/rocket.py +++ b/lib/repositories/rocket.py @@ -1,7 +1,8 @@ from typing import Union -from lib import logger, parse_error +from pymongo.errors import PyMongoError +from lib import logger from lib.models.rocket import Rocket -from lib.repositories.repo import Repository +from lib.repositories.repo import Repository, RepositoryNotInitializedException class RocketRepository(Repository): @@ -41,14 +42,17 @@ def rocket_id(self, rocket_id: "str"): self._rocket_id = rocket_id async def insert_rocket(self, rocket_data: dict): - await self.collection.insert_one(rocket_data) + collection = self.get_collection() + await collection.insert_one(rocket_data) return self async def find_rocket(self, rocket_id: str): - return await self.collection.find_one({"rocket_id": rocket_id}) + collection = self.get_collection() + return await collection.find_one({"rocket_id": rocket_id}) async def delete_rocket(self, rocket_id: str): - await self.collection.delete_one({"rocket_id": rocket_id}) + collection = self.get_collection() + await collection.delete_one({"rocket_id": rocket_id}) return self async def create_rocket( @@ -70,10 +74,10 @@ async def create_rocket( rocket_to_dict["rocket_option"] = rocket_option rocket_to_dict["motor"]["motor_kind"] = motor_kind await self.insert_rocket(rocket_to_dict) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.rocket.create_rocket: {exc_str}") - raise Exception(f"Error creating rocket: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -94,10 +98,10 @@ async def get_rocket_by_id(self, rocket_id: str) -> Union[Rocket, None]: Rocket.parse_obj(read_rocket) if read_rocket else None ) self.rocket = parsed_rocket - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.rocket.get_rocket: {exc_str}") - raise Exception(f"Error getting rocket: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: @@ -114,10 +118,10 @@ async def delete_rocket_by_id(self, rocket_id: str): """ try: await self.delete_rocket(rocket_id) - except Exception as e: - exc_str = parse_error(e) - logger.error(f"repositories.rocket.delete_rocket: {exc_str}") - raise Exception(f"Error deleting rocket: {exc_str}") from e + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e else: return self finally: diff --git a/lib/services/environment.py b/lib/services/environment.py new file mode 100644 index 0000000..e641a43 --- /dev/null +++ b/lib/services/environment.py @@ -0,0 +1,39 @@ +from typing import Self +from rocketpy.environment.environment import Environment as RocketPyEnvironment +from rocketpy.utilities import get_instance_attributes +from lib.models.environment import Env +from lib.views.environment import EnvSummary + + +class EnvironmentService(RocketPyEnvironment): + + @classmethod + def from_env_model(cls, env: Env) -> Self: + """ + Get the rocketpy env object. + + Returns: + RocketPyEnvironment + """ + rocketpy_env = cls( + latitude=env.latitude, + longitude=env.longitude, + elevation=env.elevation, + date=env.date, + ) + rocketpy_env.set_atmospheric_model( + type=env.atmospheric_model_type, file=env.atmospheric_model_file + ) + return rocketpy_env + + def get_env_summary(self) -> EnvSummary: + """ + Get the summary of the environment. + + Returns: + EnvSummary + """ + + attributes = get_instance_attributes(self) + env_summary = EnvSummary(**attributes) + return env_summary diff --git a/lib/settings/gunicorn.py b/lib/settings/gunicorn.py index b85352d..6f75825 100644 --- a/lib/settings/gunicorn.py +++ b/lib/settings/gunicorn.py @@ -7,7 +7,7 @@ def post_fork(server, worker): # pylint: disable=unused-argument uptrace.configure_opentelemetry( dsn=Secrets.get_secret("UPTRACE_DSN"), service_name="infinity-api", - service_version="1.2.0", + service_version="1.2.1", deployment_environment="production", ) from lib.api import ( # pylint: disable=import-outside-toplevel diff --git a/lib/views/environment.py b/lib/views/environment.py index b0f4757..15157db 100644 --- a/lib/views/environment.py +++ b/lib/views/environment.py @@ -2,39 +2,49 @@ from pydantic import BaseModel -class EnvData(BaseModel): - # TODO: review grav type - # grav: "Any" - elevation: int - model_type: str - model_type_max_expected_height: int - wind_speed: float - wind_direction: float - wind_heading: float - surface_pressure: float - surface_temperature: float - surface_air_density: float - surface_speed_of_sound: float - launch_date: str - lat: float - lon: float - - -class EnvPlots(BaseModel): - grid: "List[float]" - wind_speed: "List[float]" - wind_direction: "List[float]" - speed_of_sound: "List[float]" - density: "List[float]" - wind_vel_x: "List[float]" - wind_vel_y: "List[float]" - pressure: "List[float]" - temperature: "List[float]" - - class EnvSummary(BaseModel): - env_data: EnvData - env_plots: EnvPlots + latitude: float + longitude: float + elevation: float + # date: str #datetime + atmospheric_model_type: str + air_gas_constant: float + standard_g: float + earth_radius: float + datum: str + timezone: str + # ellipsoid: str # function + initial_utm_zone: int + initial_utm_letter: str + initial_north: float + initial_east: float + initial_hemisphere: str + initial_ew: str + # local_date: str #datetime + # datetime_date: str #datetime + max_expected_height: int + # barometric_height: str # function + # barometric_height_ISA: str # function + # pressure: str # function + # pressure_ISA: str # function + # temperature: str # function + # temperature_ISA: str # function + # density: str # function + # speed_of_sound: str # function + # dynamic_viscosity: str # function + # gravity: str # function + # somigliana_gravity: str # function + # wind_speed: str # function + # wind_direction: str # function + # wind_heading: str # function + # wind_velocity_x: str # function + # wind_velocity_y: str # function + # calculate_earth_radius: str # function + # decimal_degrees_to_arc_seconds: str # function + # geodesic_to_utm: str # function + # utm_to_geodesic: str # function + # prints: str # function + # plots: str # function class EnvCreated(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index 70a9e16..190673c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ dependencies = {file = ["requirements.txt"]} [project] name = "Infinity-API" -version = "1.2.0" +version = "1.2.2" description = "RESTFULL open API for rocketpy" dynamic = ["dependencies"] requires-python = ">=3.12" @@ -49,7 +49,6 @@ disable = """ too-many-public-methods, too-many-instance-attributes, logging-fstring-interpolation, - broad-exception-raised, import-error, protected-access, unnecessary-dunder-call, diff --git a/requirements.txt b/requirements.txt index 6daf7f9..ff629f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,7 @@ uvicorn rocketpy uptrace opentelemetry.instrumentation.fastapi +opentelemetry.instrumentation.requests opentelemetry-api opentelemetry-sdk +tenacity From 8aeaf82788434ef40c327aea90921f04ab8befe7 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 24 Aug 2024 18:20:56 -0300 Subject: [PATCH 2/9] refactors API architecture - wipe business logic from controllers - turn off entities deletion - replace hash ids by ObjectIds disables simulation routes exposes motor tank property and disable fronzen instances --- README.md | 60 +- lib/__init__.py | 2 +- lib/controllers/environment.py | 19 +- lib/controllers/flight.py | 366 +- lib/controllers/motor.py | 128 +- lib/controllers/rocket.py | 412 +- lib/models/aerosurfaces.py | 52 +- lib/models/environment.py | 6 +- lib/models/flight.py | 6 +- lib/models/motor.py | 69 +- lib/models/parachute.py | 26 +- lib/models/rocket.py | 67 +- lib/repositories/environment.py | 50 +- lib/repositories/flight.py | 143 +- lib/repositories/motor.py | 61 +- lib/repositories/repo.py | 16 +- lib/repositories/rocket.py | 77 +- lib/routes/environment.py | 15 +- lib/routes/flight.py | 43 +- lib/routes/motor.py | 33 +- lib/routes/rocket.py | 35 +- lib/services/flight.py | 42 + lib/services/motor.py | 82 + lib/services/rocket.py | 230 ++ lib/views/environment.py | 5 +- lib/views/flight.py | 4 +- lib/views/motor.py | 14 +- lib/views/rocket.py | 4 +- pyproject.toml | 6 +- tests/Infinity-API.postman_collection.json | 4304 -------------------- 30 files changed, 872 insertions(+), 5505 deletions(-) create mode 100644 lib/services/flight.py create mode 100644 lib/services/motor.py create mode 100644 lib/services/rocket.py delete mode 100644 tests/Infinity-API.postman_collection.json diff --git a/README.md b/README.md index 1a5c5ad..5a79e6c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ $ touch .env && echo MONGODB_CONNECTION_STRING="$ConnectionString" > .env ### Docker - run docker compose: `docker-compose up --build -d` -### Standard +### Standalone - Dev: `python3 -m uvicorn lib:app --reload --port 3000` - Prod: `gunicorn -k uvicorn.workers.UvicornWorker lib:app -b 0.0.0.0:3000` @@ -38,6 +38,12 @@ $ touch .env && echo MONGODB_CONNECTION_STRING="$ConnectionString" > .env │   │   ├── motor.py │   │   └── rocket.py │   │   +│   ├── services +│   │   ├── environment.py +│   │   ├── flight.py +│   │   ├── motor.py +│   │   └── rocket.py +│   │   │   ├── routes │   │   ├── environment.py │   │   ├── flight.py @@ -66,43 +72,51 @@ $ touch .env && echo MONGODB_CONNECTION_STRING="$ConnectionString" > .env │   └── rocket.py │   └── tests - ├── infinity-api-postman-collection.json - │   ├── integration + │   ├── test_environment_integration.py + │   ├── test_motor_integration.py + │   ├── test_rocket_integration.py + │   └── test_flight_integration.py │   └── unit ├── test_secrets.py ├── test_api.py │   ├── test_controllers - │   ├── test_environment.py - │   ├── test_flight.py - │   ├── test_motor.py - │   └── test_rocket.py + │   ├── test_environment_controller.py + │   ├── test_flight_controller.py + │   ├── test_motor_controller.py + │   └── test_rocket_controller.py + │   + ├── test_services + │   ├── test_environment_service.py + │   ├── test_flight_service.py + │   ├── test_motor_service.py + │   └── test_rocket_serice.py │ ├── test_routes - │   ├── test_environment.py - │   ├── test_flight.py - │   ├── test_motor.py - │   └── test_rocket.py + │   ├── test_environment_route.py + │   ├── test_flight_route.py + │   ├── test_motor_route.py + │   └── test_rocket_route.py │ ├── test_repositories - │   ├── test_environment.py - │   ├── test_flight.py - │   ├── test_motor.py - │   └── test_rocket.py + │   ├── test_environment_repo.py + │   ├── test_flight_repo.py + │   ├── test_motor_repo.py + │   └── test_rocket_repo.py │ ├── test_models - │   ├── test_environment.py - │   ├── test_flight.py - │   ├── test_motor.py - │   └── test_rocket.py + │   ├── test_environment_model.py + │   ├── test_flight_model.py + │   ├── test_motor_model.py + │   └── test_rocket_model.py │   └── test_views -    ├── test_environment.py -    ├── test_flight.py -    ├── test_motor.py -    └── test_rocket.py +    ├── test_environment_view.py +    ├── test_flight_view.py +    ├── test_motor_view.py +    └── test_rocket_view.py ``` ## DOCS diff --git a/lib/__init__.py b/lib/__init__.py index 0dbaa09..87832bf 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -22,7 +22,7 @@ def parse_error(error): exc_type = type(error).__name__ exc_obj = f"{error}".replace("\n", " ").replace(" ", " ") - return f"{exc_type} exception: {exc_obj}" + return f"{exc_type}: {exc_obj}" from lib.api import app diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index 9bb71e7..e8a58ec 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -48,8 +48,7 @@ async def create_env(self) -> Union[EnvCreated, HTTPException]: views.EnvCreated """ try: - async with EnvRepository() as env_repo: - env_repo.fetch_env(self.env) + async with EnvRepository(self.env) as env_repo: await env_repo.create_env() except PyMongoError as e: logger.error( @@ -69,10 +68,10 @@ async def create_env(self) -> Union[EnvCreated, HTTPException]: detail=f"Failed to create environment: {exc_str}", ) from e else: - return EnvCreated(env_id=self.env.env_id) + return EnvCreated(env_id=env_repo.env_id) finally: logger.info( - f"Call to controllers.environment.create_env completed for Env {hash(self.env)}" + f"Call to controllers.environment.create_env completed for Env {env_repo.env_id}" ) @staticmethod @@ -178,10 +177,8 @@ async def update_env_by_id( HTTP 404 Not Found: If the env is not found in the database. """ try: - async with EnvRepository() as env_repo: - env_repo.fetch_env(self.env) - await env_repo.create_env() - await env_repo.delete_env_by_id(env_id) + async with EnvRepository(self.env) as env_repo: + await env_repo.update_env_by_id(env_id) except PyMongoError as e: logger.error( f"controllers.environment.update_env: PyMongoError {e}" @@ -200,10 +197,10 @@ async def update_env_by_id( detail=f"Failed to update environment: {exc_str}", ) from e else: - return EnvUpdated(new_env_id=self.env.env_id) + return EnvUpdated(env_id=env_id) finally: logger.info( - f"Call to controllers.environment.update_env completed for Env {env_id}; Env {hash(self.env)}" + f"Call to controllers.environment.update_env completed for Env {env_id}" ) @staticmethod @@ -243,7 +240,7 @@ async def delete_env_by_id( detail=f"Failed to delete environment: {exc_str}", ) from e else: - return EnvDeleted(deleted_env_id=env_id) + return EnvDeleted(env_id=env_id) finally: logger.info( f"Call to controllers.environment.delete_env completed for Env {env_id}" diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index af2b307..359be3d 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -2,37 +2,22 @@ from fastapi import HTTPException, status from pymongo.errors import PyMongoError -from rocketpy.simulation.flight import Flight as RocketPyFlight import jsonpickle from lib import logger, parse_error -from lib.models.rocket import Rocket, RocketOptions -from lib.models.motor import MotorKinds -from lib.models.flight import Flight from lib.models.environment import Env +from lib.models.rocket import Rocket +from lib.models.flight import Flight 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 -from lib.services.environment import EnvironmentService +from lib.services.flight import FlightService class FlightController: @@ -56,49 +41,17 @@ class FlightController: def __init__( self, flight: Flight, - *, - rocket_option: RocketOptions, - motor_kind: MotorKinds, ): self._flight = flight - self._rocket_option = rocket_option - self._motor_kind = motor_kind @property def flight(self) -> Flight: return self._flight - @property - def rocket_option(self) -> RocketOptions: - return self._rocket_option - - @property - def motor_kind(self) -> MotorKinds: - return self._motor_kind - @flight.setter def flight(self, flight: Flight): self._flight = flight - @staticmethod - def get_rocketpy_flight(flight: Flight) -> RocketPyFlight: - """ - Get the rocketpy flight object. - - Returns: - RocketPyFlight - """ - rocketpy_rocket = RocketController.get_rocketpy_rocket(flight.rocket) - rocketpy_env = EnvironmentService.from_env_model(flight.environment) - rocketpy_flight = RocketPyFlight( - rocket=rocketpy_rocket, - inclination=flight.inclination, - heading=flight.heading, - environment=rocketpy_env, - rail_length=flight.rail_length, - ) - return rocketpy_flight - async def create_flight(self) -> Union[FlightCreated, HTTPException]: """ Create a flight in the database. @@ -107,12 +60,8 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: views.FlightCreated """ try: - async with FlightRepository() as flight_repo: - flight_repo.fetch_flight(self.flight) - await flight_repo.create_flight( - motor_kind=self.motor_kind, - rocket_option=self.rocket_option, - ) + async with FlightRepository(self.flight) as flight_repo: + await flight_repo.create_flight() except PyMongoError as e: logger.error(f"controllers.flight.create_flight: PyMongoError {e}") raise HTTPException( @@ -129,10 +78,10 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: detail=f"Failed to create flight: {exc_str}", ) from e else: - return FlightCreated(flight_id=self.flight.flight_id) + return FlightCreated(flight_id=flight_repo.flight_id) finally: logger.info( - f"Call to controllers.flight.create_flight completed for Flight {hash(self.flight)}" + f"Call to controllers.flight.create_flight completed for Flight {flight_repo.flight_id}" ) @staticmethod @@ -201,7 +150,7 @@ async def get_rocketpy_flight_as_jsonpickle( """ try: read_flight = await cls.get_flight_by_id(flight_id) - rocketpy_flight = cls.get_rocketpy_flight(read_flight) + rocketpy_flight = FlightService.from_flight_model(read_flight) except HTTPException as e: raise e from e except Exception as e: @@ -238,13 +187,8 @@ async def update_flight_by_id( HTTP 404 Not Found: If the flight is not found in the database. """ try: - async with FlightRepository() as flight_repo: - flight_repo.fetch_flight(self.flight) - await flight_repo.create_flight( - motor_kind=self.motor_kind, - rocket_option=self.rocket_option, - ) - await flight_repo.delete_flight_by_id(flight_id) + async with FlightRepository(self.flight) as flight_repo: + await flight_repo.update_flight_by_id(flight_id) except PyMongoError as e: logger.error(f"controllers.flight.update_flight: PyMongoError {e}") raise HTTPException( @@ -261,7 +205,7 @@ async def update_flight_by_id( detail=f"Failed to update flight: {exc_str}", ) from e else: - return FlightUpdated(new_flight_id=self.flight.flight_id) + return FlightUpdated(flight_id=flight_id) finally: logger.info( f"Call to controllers.flight.update_flight completed for Flight {flight_id}" @@ -289,13 +233,8 @@ async def update_env_by_flight_id( new_flight = read_flight.dict() new_flight["environment"] = env new_flight = Flight(**new_flight) - async with FlightRepository() as flight_repo: - flight_repo.fetch_flight(new_flight) - await flight_repo.create_flight( - motor_kind=read_flight.rocket.motor.motor_kind, - rocket_option=read_flight.rocket.rocket_option, - ) - await flight_repo.delete_flight_by_id(flight_id) + async with FlightRepository(new_flight) as flight_repo: + await flight_repo.update_env_by_flight_id(flight_id) except PyMongoError as e: logger.error( f"controllers.flight.update_env_by_flight_id: PyMongoError {e}" @@ -314,15 +253,15 @@ async def update_env_by_flight_id( detail=f"Failed to update environment: {exc_str}", ) from e else: - return FlightUpdated(new_flight_id=new_flight.flight_id) + return FlightUpdated(flight_id=flight_id) finally: logger.info( - f"Call to controllers.flight.update_env completed for Flight {flight_id}; Env {hash(env)}" + f"Call to controllers.flight.update_env completed for Flight {flight_id}" ) @classmethod async def update_rocket_by_flight_id( - cls, flight_id: str, *, rocket: Rocket, rocket_option, motor_kind + cls, flight_id: str, *, rocket: Rocket ) -> Union[FlightUpdated, HTTPException]: """ Update a models.Flight.rocket in the database. @@ -340,17 +279,15 @@ async def update_rocket_by_flight_id( try: read_flight = await cls.get_flight_by_id(flight_id) updated_rocket = rocket.dict() - updated_rocket["rocket_option"] = rocket_option - updated_rocket["motor"]["motor_kind"] = motor_kind + updated_rocket["rocket_option"] = rocket.rocket_option.value + updated_rocket["motor"][ + "motor_kind" + ] = rocket.motor.motor_kind.value new_flight = read_flight.dict() new_flight["rocket"] = updated_rocket new_flight = Flight(**new_flight) - async with FlightRepository() as flight_repo: - flight_repo.fetch_flight(new_flight) - await flight_repo.create_flight( - motor_kind=motor_kind, rocket_option=rocket_option - ) - await flight_repo.delete_flight_by_id(flight_id) + async with FlightRepository(new_flight) as flight_repo: + await flight_repo.update_rocket_by_flight_id(flight_id) except PyMongoError as e: logger.error( f"controllers.flight.update_rocket_by_flight_id: PyMongoError {e}" @@ -369,10 +306,10 @@ async def update_rocket_by_flight_id( detail=f"Failed to update rocket: {exc_str}", ) from e else: - return FlightUpdated(new_flight_id=new_flight.flight_id) + return FlightUpdated(flight_id=flight_id) finally: logger.info( - f"Call to controllers.flight.update_rocket completed for Flight {flight_id}; Rocket {hash(rocket)}" + f"Call to controllers.flight.update_rocket completed for Flight {flight_id}" ) @staticmethod @@ -410,7 +347,7 @@ async def delete_flight_by_id( detail=f"Failed to delete flight: {exc_str}", ) from e else: - return FlightDeleted(deleted_flight_id=flight_id) + return FlightDeleted(flight_id=flight_id) finally: logger.info( f"Call to controllers.flight.delete_flight completed for Flight {flight_id}" @@ -435,257 +372,8 @@ async def simulate_flight( """ try: read_flight = await cls.get_flight_by_id(flight_id=flight_id) - flight = cls.get_rocketpy_flight(read_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) - ), - initial_velocity="Velocity - Vx: {:.2f} m/s | Vy: {:.2f} m/s | Vz: {:.2f} m/s".format( - flight.vx(0), flight.vy(0), flight.vz(0) - ), - initial_position="Position - x: {:.2f} m | y: {:.2f} m | z: {:.2f} m".format( - flight.x(0), flight.y(0), flight.z(0) - ), - initial_angular_position="Euler Angles - Spin φ : {:.2f}° | Nutation θ: {:.2f}° | Precession ψ: {:.2f}°".format( - flight.phi(0), flight.theta(0), flight.psi(0) - ), - initial_angular_velocity="Angular Velocity - ω1: {:.2f} rad/s | ω2: {:.2f} rad/s| ω3: {:.2f} rad/s".format( - flight.w1(0), flight.w2(0), flight.w3(0) - ), - ) - - _numerical_integration_settings = NumericalIntegrationSettings( - max_time="Maximum Allowed Flight Time: {:f} s".format( - flight.max_time - ), - max_time_step="Maximum Allowed Time Step: {:f} s".format( - flight.max_time_step - ), - min_time_step="Minimum Allowed Time Step: {:e} s".format( - flight.min_time_step - ), - relative_error_tolerance=f"Relative Error Tolerance: {flight.rtol}", - absolute_error_tolerance=f"Absolute Error Tolerance: {flight.atol}", - time_overshoot=f"Allow Event Overshoot: {flight.time_overshoot}", - terminate_on_apogee=f"Terminate Simulation on Apogee: {flight.terminate_on_apogee}", - number_of_time_steps=f"Number of Time Steps Used: {len(flight.time_steps)}", - function_evaluations_per_time_step=f"Number of Derivative Functions Evaluation: {sum(flight.function_evaluations_per_time_step)}", - avg_function_evaluations_per_time_step="Average Function Evaluations per Time Step: {:3f}".format( - sum(flight.function_evaluations_per_time_step) - / len(flight.time_steps) - ), - ) - - _launch_rail_conditions = LaunchRailConditions( - rail_length="Launch Rail Length: {:.2f} m".format( - flight.rail_length - ), - flight_inclination="Launch Rail Inclination: {:.2f}°".format( - flight.inclination - ), - flight_heading="Launch Rail Heading: {:.2f}°".format( - flight.heading - ), - ) - - _surface_wind_conditions = SurfaceWindConditions( - frontal_surface_wind_speed="Frontal Surface Wind Speed: {:.2f} m/s".format( - flight.frontal_surface_wind - ), - lateral_surface_wind_speed="Lateral Surface Wind Speed: {:.2f} m/s".format( - flight.lateral_surface_wind - ), - ) - - _out_of_rail_conditions = OutOfRailConditions( - out_of_rail_time="Rail Departure Time: {:.3f} s".format( - flight.out_of_rail_time - ), - out_of_rail_velocity="Rail Departure Velocity: {:.3f} m/s".format( - flight.out_of_rail_velocity - ), - out_of_rail_static_margin="Rail Departure Static Margin: {:.3f} c".format( - flight.rocket.static_margin(flight.out_of_rail_time) - ), - out_of_rail_angle_of_attack="Rail Departure Angle of Attack: {:.3f}°".format( - flight.angle_of_attack(flight.out_of_rail_time) - ), - out_of_rail_thrust_weight_ratio="Rail Departure Thrust-Weight Ratio: {:.3f}".format( - flight.rocket.thrust_to_weight(flight.out_of_rail_time) - ), - out_of_rail_reynolds_number="Rail Departure Reynolds Number: {:.3e}".format( - flight.reynolds_number(flight.out_of_rail_time) - ), - ) - - _burnout_conditions = BurnoutConditions( - burnout_time="Burn out time: {:.3f} s".format( - flight.rocket.motor.burn_out_time - ), - burnout_altitude="Altitude at burn out: {:.3f} m (AGL)".format( - flight.z(flight.rocket.motor.burn_out_time) - - flight.env.elevation - ), - burnout_rocket_velocity="Rocket velocity at burn out: {:.3f} m/s".format( - flight.speed(flight.rocket.motor.burn_out_time) - ), - burnout_freestream_velocity="Freestream velocity at burn out: {:.3f} m/s".format( - ( - flight.stream_velocity_x( - flight.rocket.motor.burn_out_time - ) - ** 2 - + flight.stream_velocity_y( - flight.rocket.motor.burn_out_time - ) - ** 2 - + flight.stream_velocity_z( - flight.rocket.motor.burn_out_time - ) - ** 2 - ) - ** 0.5 - ), - burnout_mach_number="Mach Number at burn out: {:.3f}".format( - flight.mach_number(flight.rocket.motor.burn_out_time) - ), - burnout_kinetic_energy="Kinetic energy at burn out: {:.3e}".format( - flight.kinetic_energy(flight.rocket.motor.burn_out_time) - ), - ) - - _apogee_conditions = ApogeeConditions( - apogee_altitude="Apogee Altitude: {:.3f} m (ASL) | {:.3f} m (AGL)".format( - flight.apogee, flight.apogee - flight.env.elevation - ), - apogee_time="Apogee Time: {:.3f} s".format(flight.apogee_time), - apogee_freestream_speed="Apogee Freestream Speed: {:.3f} m/s".format( - flight.apogee_freestream_speed - ), - ) - - _maximum_values = MaximumValues( - maximum_speed="Maximum Speed: {:.3f} m/s at {:.2f} s".format( - flight.max_speed, flight.max_speed_time - ), - maximum_mach_number="Maximum Mach Number: {:.3f} Mach at {:.2f} s".format( - flight.max_mach_number, flight.max_mach_number_time - ), - maximum_reynolds_number="Maximum Reynolds Number: {:.3e} at {:.2f} s".format( - flight.max_reynolds_number, flight.max_reynolds_number_time - ), - maximum_dynamic_pressure="Maximum Dynamic Pressure: {:.3e} Pa at {:.2f} s".format( - flight.max_dynamic_pressure, - flight.max_dynamic_pressure_time, - ), - maximum_acceleration_during_motor_burn="Maximum Acceleration During Motor Burn: {:.3f} m/s² at {:.2f} s".format( - flight.max_acceleration, flight.max_acceleration_time - ), - maximum_gs_during_motor_burn="Maximum Gs During Motor Burn: {:.3f} g at {:.2f} s".format( - flight.max_acceleration - / flight.env.gravity( - flight.z(flight.max_acceleration_time) - ), - flight.max_acceleration_time, - ), - maximum_acceleration_after_motor_burn="Maximum Acceleration After Motor Burn: {:.3f} m/s² at {:.2f} s".format( - flight.max_acceleration_power_off, - flight.max_acceleration_power_off_time, - ), - maximum_gs_after_motor_burn="Maximum Gs After Motor Burn: {:.3f} g at {:.2f} s".format( - flight.max_acceleration_power_off / flight.env.standard_g, - flight.max_acceleration_power_off_time, - ), - maximum_upper_rail_button_normal_force="Maximum Upper Rail Button Normal Force: {:.3f} N".format( - flight.max_rail_button1_normal_force - ), - maximum_upper_rail_button_shear_force="Maximum Upper Rail Button Shear Force: {:.3f} N".format( - flight.max_rail_button1_shear_force - ), - maximum_lower_rail_button_normal_force="Maximum Lower Rail Button Normal Force: {:.3f} N".format( - flight.max_rail_button2_normal_force - ), - maximum_lower_rail_button_shear_force="Maximum Lower Rail Button Shear Force: {:.3f} N".format( - flight.max_rail_button2_shear_force - ), - ) - - if len(flight.impact_state) != 0: - _impact_conditions = ImpactConditions( - x_impact_position="X Impact: {:.3f} m".format( - flight.x_impact - ), - y_impact_position="Y Impact: {:.3f} m".format( - flight.y_impact - ), - time_of_impact="Time of Impact: {:.3f} s".format( - flight.t_final - ), - impact_velocity="Velocity at Impact: {:.3f} m/s".format( - flight.impact_velocity - ), - ) - elif flight.terminate_on_apogee is False: - _impact_conditions = ImpactConditions( - time="Time: {:.3f} s".format(flight.solution[-1][0]), - altitude="Altitude: {:.3f} m".format( - flight.solution[-1][3] - ), - ) - - if len(flight.parachute_events) == 0: - _events_registered = EventsRegistered( - events_trace="No parachute event registered" - ) - else: - events = {} - for event in flight.parachute_events: - trigger_time = event[0] - parachute = event[1] - open_time = trigger_time + parachute.lag - velocity = flight.free_stream_speed(open_time) - altitude = flight.z(open_time) - name = parachute.name.title() - events[name] = [] - events[name].append( - name - + " Ejection Triggered at: {:.3f} s".format( - trigger_time - ) - ) - events[name].append( - name - + " Parachute Inflated at: {:.3f} s".format(open_time) - ) - events[name].append( - name - + " Parachute Inflated with Freestream Speed of: {:.3f} m/s".format( - velocity - ) - ) - events[name].append( - name - + " Parachute Inflated at Height of: {:.3f} m (AGL)".format( - altitude - flight.env.elevation - ) - ) - _events_registered = EventsRegistered(events_trace=events) - - _flight_data = FlightData( - initial_conditions=_initial_conditions, - numerical_integration_settings=_numerical_integration_settings, - surface_wind_conditions=_surface_wind_conditions, - launch_rail_conditions=_launch_rail_conditions, - out_of_rail_conditions=_out_of_rail_conditions, - burnout_conditions=_burnout_conditions, - apogee_conditions=_apogee_conditions, - maximum_values=_maximum_values, - impact_conditions=_impact_conditions, - events_registered=_events_registered, - ) - - flight_summary = FlightSummary(flight_data=_flight_data) + rocketpy_flight = FlightService.from_flight_model(read_flight) + flight_summary = rocketpy_flight.get_flight_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 2ba8588..23bf8ad 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -1,17 +1,14 @@ from typing import Union from fastapi import HTTPException, status from pymongo.errors import PyMongoError -from rocketpy.motors.solid_motor import SolidMotor -from rocketpy.motors.liquid_motor import LiquidMotor -from rocketpy.motors.hybrid_motor import HybridMotor import jsonpickle from lib import logger, parse_error from lib.models.motor import Motor, MotorKinds +from lib.services.motor import MotorService from lib.repositories.motor import MotorRepository from lib.views.motor import ( MotorSummary, - MotorData, MotorCreated, MotorUpdated, MotorDeleted, @@ -30,10 +27,9 @@ class MotorController: - Create a rocketpy.Motor object from a Motor model object. """ - def __init__(self, *, motor: Motor, motor_kind: MotorKinds): - self.guard(motor, motor_kind) + def __init__(self, motor: Motor): + self.guard(motor) self._motor = motor - self._motor_kind = motor_kind @property def motor(self) -> Motor: @@ -43,67 +39,8 @@ def motor(self) -> Motor: def motor(self, motor: Motor): self._motor = motor - @property - def motor_kind(self) -> MotorKinds: - return self._motor_kind - - @staticmethod - def get_rocketpy_motor( - motor: Motor, - ) -> Union[LiquidMotor, HybridMotor, SolidMotor]: - """ - Get the rocketpy motor object. - - Returns: - Union[LiquidMotor, HybridMotor, SolidMotor] - """ - - motor_core = { - "thrust_source": f"lib/data/engines/{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.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.motor_kind != MotorKinds.SOLID: - for tank in motor.tanks: - rocketpy_motor.add_tank(tank.tank, tank.position) - - return rocketpy_motor - - def guard(self, motor: Motor, motor_kind): - if motor_kind != MotorKinds.SOLID and motor.tanks is None: + def guard(self, motor: Motor): + if motor.motor_kind != MotorKinds.SOLID and motor.tanks is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Tanks must be provided for liquid and hybrid motors.", @@ -117,9 +54,8 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: views.MotorCreated """ try: - async with MotorRepository() as motor_repo: - motor_repo.fetch_motor(self.motor) - await motor_repo.create_motor(motor_kind=self.motor_kind) + async with MotorRepository(self.motor) as motor_repo: + await motor_repo.create_motor() except PyMongoError as e: logger.error(f"controllers.motor.create_motor: PyMongoError {e}") raise HTTPException( @@ -136,10 +72,10 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: detail=f"Failed to create motor: {exc_str}", ) from e else: - return MotorCreated(motor_id=self.motor.motor_id) + return MotorCreated(motor_id=motor_repo.motor_id) finally: logger.info( - f"Call to controllers.motor.create_motor completed for Motor {self.motor.motor_id}" + f"Call to controllers.motor.create_motor completed for Motor {motor_repo.motor_id}" ) @staticmethod @@ -208,7 +144,7 @@ async def get_rocketpy_motor_as_jsonpickle( """ try: read_motor = await cls.get_motor_by_id(motor_id) - rocketpy_motor = cls.get_rocketpy_motor(read_motor) + rocketpy_motor = MotorService.from_motor_model(read_motor) except HTTPException as e: raise e from e except Exception as e: @@ -245,10 +181,8 @@ async def update_motor_by_id( HTTP 404 Not Found: If the motor is not found in the database. """ try: - async with MotorRepository() as motor_repo: - motor_repo.fetch_motor(self.motor) - await motor_repo.create_motor(motor_kind=self.motor_kind) - await motor_repo.delete_motor_by_id(motor_id) + async with MotorRepository(self.motor) as motor_repo: + await motor_repo.update_motor_by_id(motor_id) except PyMongoError as e: logger.error(f"controllers.motor.update_motor: PyMongoError {e}") raise HTTPException( @@ -265,7 +199,7 @@ async def update_motor_by_id( detail=f"Failed to update motor: {exc_str}", ) from e else: - return MotorUpdated(new_motor_id=self.motor.motor_id) + return MotorUpdated(motor_id=motor_id) finally: logger.info( f"Call to controllers.motor.update_motor completed for Motor {motor_id}" @@ -306,7 +240,7 @@ async def delete_motor_by_id( detail=f"Failed to delete motor: {exc_str}", ) from e else: - return MotorDeleted(deleted_motor_id=motor_id) + return MotorDeleted(motor_id=motor_id) finally: logger.info( f"Call to controllers.motor.delete_motor completed for Motor {motor_id}" @@ -330,38 +264,8 @@ async def simulate_motor( """ try: read_motor = await cls.get_motor_by_id(motor_id) - motor = cls.get_rocketpy_motor(read_motor) - - motor_simulation_numbers = MotorData( - total_burning_time="Total Burning Time: " - + str(motor.burn_out_time) - + " s", - total_propellant_mass="Total Propellant Mass: " - + "{:.3f}".format(motor.propellant_initial_mass) - + " kg", - average_propellant_exhaust_velocity="Average Propellant Exhaust Velocity: " - + "{:.3f}".format( - motor.exhaust_velocity.average(*motor.burn_time) - ) - + " m/s", - average_thrust="Average Thrust: " - + "{:.3f}".format(motor.average_thrust) - + " N", - maximum_thrust="Maximum Thrust: " - + str(motor.max_thrust) - + " N at " - + str(motor.max_thrust_time) - + " s after ignition.", - total_impulse="Total Impulse: " - + "{:.3f}".format(motor.total_impulse) - + " Ns\n", - ) - # motor_simulation_plots = MotorPlots( - # motor.thrust.plot(lower=lower_limit, upper=upper_limit) - # ) - motor_summary = MotorSummary( - motor_data=motor_simulation_numbers - ) # , plots=motor_simulation_plots ) + rocketpy_motor = MotorService.from_motor_model(read_motor) + motor_summary = rocketpy_motor.get_motor_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index 90b54a3..cddec3d 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -1,6 +1,4 @@ from typing import Union -import os -import ast import jsonpickle from fastapi import HTTPException, status @@ -9,27 +7,11 @@ # TODO # from inspect import getsourcelines -from rocketpy.rocket.parachute import Parachute as RocketPyParachute -from rocketpy.rocket.rocket import Rocket as RocketPyRocket -from rocketpy.rocket.aero_surface import NoseCone as RocketPyNoseCone -from rocketpy.rocket.aero_surface import ( - TrapezoidalFins as RocketPyTrapezoidalFins, -) -from rocketpy.rocket.aero_surface import Tail as RocketPyTail - from lib import logger, parse_error -from lib.controllers.motor import MotorController -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.services.rocket import RocketService +from lib.models.rocket import Rocket from lib.repositories.rocket import RocketRepository from lib.views.rocket import ( - InertiaDetails, - RocketGeometricalParameters, - RocketAerodynamicsQuantities, - ParachuteData, - RocketData, RocketSummary, RocketCreated, RocketUpdated, @@ -51,14 +33,9 @@ class RocketController: def __init__( self, - *, rocket: Rocket, - rocket_option: RocketOptions, - motor_kind: MotorKinds, ): self._rocket = rocket - self._rocket_option = rocket_option - self._motor_kind = motor_kind @property def rocket(self) -> Rocket: @@ -68,84 +45,6 @@ def rocket(self) -> Rocket: def rocket(self, rocket: Rocket): self._rocket = rocket - @property - def rocket_option(self) -> RocketOptions: - return self._rocket_option - - @property - def motor_kind(self) -> MotorKinds: - return self._motor_kind - - @classmethod - def get_rocketpy_rocket(cls, rocket: Rocket) -> RocketPyRocket: - """ - Get a rocketpy rocket object. - - Returns: - RocketPyRocket - """ - - rocketpy_rocket = RocketPyRocket( - radius=rocket.radius, - mass=rocket.mass, - inertia=rocket.inertia, - power_off_drag=os.path.join( - "lib/data", - f"{rocket.rocket_option.lower()}", - "powerOffDragCurve.csv", - ), - power_on_drag=os.path.join( - "lib/data", - f"{rocket.rocket_option.lower()}", - "powerOnDragCurve.csv", - ), - center_of_mass_without_motor=rocket.center_of_mass_without_motor, - coordinate_system_orientation=rocket.coordinate_system_orientation, - ) - - # RailButtons - 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.get_rocketpy_motor(rocket.motor), - rocket.motor_position, - ) - - # NoseCone - nose = cls.get_rocketpy_nose(rocket.nose) - rocketpy_rocket.aerodynamic_surfaces.add(nose, nose.position) - rocketpy_rocket.evaluate_static_margin() - - # FinSet - # TODO: re-write this to match overall fins not only TrapezoidalFins - # Maybe a strategy with different factory methods? - finset = cls.get_rocketpy_finset(rocket.fins) - rocketpy_rocket.aerodynamic_surfaces.add(finset, finset.position) - rocketpy_rocket.evaluate_static_margin() - - # Tail - tail = cls.get_rocketpy_tail(rocket.tail) - rocketpy_rocket.aerodynamic_surfaces.add(tail, tail.position) - rocketpy_rocket.evaluate_static_margin() - - # Parachutes - for p in range(len(rocket.parachutes)): - parachute_trigger = rocket.parachutes[p].triggers[0] - if cls.check_parachute_trigger(parachute_trigger): - rocket.parachutes[p].triggers[0] = compile( - parachute_trigger, "", "eval" - ) - parachute = cls.get_rocketpy_parachute(rocket.parachutes, p) - rocketpy_rocket.parachutes.append(parachute) - else: - print("Parachute trigger not valid. Skipping parachute.") - continue - - return rocketpy_rocket - async def create_rocket(self) -> Union[RocketCreated, HTTPException]: """ Create a models.Rocket in the database. @@ -154,12 +53,8 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: views.RocketCreated """ try: - async with RocketRepository() as rocket_repo: - rocket_repo.fetch_rocket(self.rocket) - await rocket_repo.create_rocket( - rocket_option=self.rocket_option, - motor_kind=self.motor_kind, - ) + async with RocketRepository(self.rocket) as rocket_repo: + await rocket_repo.create_rocket() except PyMongoError as e: logger.error(f"controllers.rocket.create_rocket: PyMongoError {e}") raise HTTPException( @@ -176,10 +71,10 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: detail=f"Failed to create rocket: {exc_str}", ) from e else: - return RocketCreated(rocket_id=self.rocket.rocket_id) + return RocketCreated(rocket_id=rocket_repo.rocket_id) finally: logger.info( - f"Call to controllers.rocket.create_rocket completed for Rocket {hash(self.rocket)}" + f"Call to controllers.rocket.create_rocket completed for Rocket {rocket_repo.rocket_id}" ) @staticmethod @@ -249,7 +144,7 @@ async def get_rocketpy_rocket_as_jsonpickle( """ try: read_rocket = await cls.get_rocket_by_id(rocket_id) - rocketpy_rocket = cls.get_rocketpy_rocket(read_rocket) + rocketpy_rocket = RocketService.from_rocket_model(read_rocket) except HTTPException as e: raise e from e except Exception as e: @@ -286,13 +181,8 @@ async def update_rocket_by_id( HTTP 404 Not Found: If the rocket is not found in the database. """ try: - async with RocketRepository() as rocket_repo: - rocket_repo.fetch_rocket(self.rocket) - await rocket_repo.create_rocket( - rocket_option=self.rocket_option, - motor_kind=self.motor_kind, - ) - await rocket_repo.delete_rocket_by_id(rocket_id) + async with RocketRepository(self.rocket) as rocket_repo: + await rocket_repo.update_rocket_by_id(rocket_id) except PyMongoError as e: logger.error(f"controllers.rocket.update_rocket: PyMongoError {e}") raise HTTPException( @@ -309,7 +199,7 @@ async def update_rocket_by_id( detail=f"Failed to update rocket: {exc_str}", ) from e else: - return RocketUpdated(new_rocket_id=self.rocket.rocket_id) + return RocketUpdated(rocket_id=rocket_id) finally: logger.info( f"Call to controllers.rocket.update_rocket completed for Rocket {rocket_id}" @@ -350,7 +240,7 @@ async def delete_rocket_by_id( detail=f"Failed to delete rocket: {exc_str}", ) from e else: - return RocketDeleted(deleted_rocket_id=rocket_id) + return RocketDeleted(rocket_id=rocket_id) finally: logger.info( f"Call to controllers.rocket.delete_rocket completed for Rocket {rocket_id}" @@ -375,146 +265,8 @@ async def simulate_rocket( """ try: read_rocket = await cls.get_rocket_by_id(rocket_id) - rocket = cls.get_rocketpy_rocket(read_rocket) - - _inertia_details = InertiaDetails( - rocket_mass_without_propellant="Rocket Mass: {:.3f} kg (No Propellant)".format( - rocket.mass - ), - rocket_mass_with_propellant="Rocket Mass: {:.3f} kg (With Propellant)".format( - rocket.total_mass(0) - ), - rocket_inertia_with_motor_without_propellant=[ - "Rocket Inertia (with motor, but without propellant) 11: {:.3f} kg*m2".format( - rocket.dry_I_11 - ), - "Rocket Inertia (with motor, but without propellant) 22: {:.3f} kg*m2".format( - rocket.dry_I_22 - ), - "Rocket Inertia (with motor, but without propellant) 33: {:.3f} kg*m2".format( - rocket.dry_I_33 - ), - "Rocket Inertia (with motor, but without propellant) 12: {:.3f} kg*m2".format( - rocket.dry_I_12 - ), - "Rocket Inertia (with motor, but without propellant) 13: {:.3f} kg*m2".format( - rocket.dry_I_13 - ), - "Rocket Inertia (with motor, but without propellant) 23: {:.3f} kg*m2".format( - rocket.dry_I_23 - ), - ], - ) - - _rocket_geometrical_parameters = RocketGeometricalParameters( - rocket_maximum_radius="Rocket Maximum Radius: " - + str(rocket.radius) - + " m", - rocket_frontal_area="Rocket Frontal Area: " - + "{:.6f}".format(rocket.area) - + " m2", - rocket_codm_nozzle_exit_distance="Rocket Center of Dry Mass - Nozzle Exit Distance: " - + "{:.3f} m".format( - abs( - rocket.center_of_dry_mass_position - - rocket.motor_position - ) - ), - rocket_codm_center_of_propellant_mass="Rocket Center of Dry Mass - Center of Propellant Mass: " - + "{:.3f} m".format( - abs( - rocket.center_of_propellant_position(0) - - rocket.center_of_dry_mass_position - ) - ), - rocket_codm_loaded_center_of_mass="Rocket Center of Mass - Rocket Loaded Center of Mass: " - + "{:.3f} m".format( - abs( - rocket.center_of_mass(0) - - rocket.center_of_dry_mass_position - ) - ), - ) - - _aerodynamics_lift_coefficient_derivatives = {} - for surface, _position in rocket.aerodynamic_surfaces: - name = surface.name - _aerodynamics_lift_coefficient_derivatives[name] = [] - _aerodynamics_lift_coefficient_derivatives[name].append( - name - + " Lift Coefficient Derivative: {:.3f}".format( - surface.clalpha(0) - ) - + "/rad" - ) - - _aerodynamics_center_of_pressure = {} - for surface, _position in rocket.aerodynamic_surfaces: - name = surface.name - cpz = surface.cp[2] - _aerodynamics_center_of_pressure[name] = [] - _aerodynamics_center_of_pressure[name].append( - name - + " Center of Pressure to CM: {:.3f}".format(cpz) - + " m" - ) - - _rocket_aerodynamics_quantities = RocketAerodynamicsQuantities( - aerodynamics_lift_coefficient_derivatives=_aerodynamics_lift_coefficient_derivatives, - aerodynamics_center_of_pressure=_aerodynamics_center_of_pressure, - distance_cop_to_codm="Distance from Center of Pressure to Center of Dry Mass: " - + "{:.3f}".format( - rocket.center_of_mass(0) - rocket.cp_position(0) - ) - + " m", - initial_static_margin="Initial Static Margin: " - + "{:.3f}".format(rocket.static_margin(0)) - + " c", - final_static_margin="Final Static Margin: " - + "{:.3f}".format( - rocket.static_margin(rocket.motor.burn_out_time) - ) - + " c", - ) - - _parachute_details = {} - _parachute_ejection_signal_trigger = {} - _parachute_ejection_system_refresh_rate = {} - _parachute_lag = {} - for chute in rocket.parachutes: - _parachute_details[chute.name] = chute.__str__() - - if chute.trigger.__name__ == "": - # line = getsourcelines(chute.trigger)[0][0] - # _parachute_ejection_signal_trigger[chute.name] = "Ejection signal trigger: " + line.split("lambda ")[1].split(",")[0].split("\n")[0] - pass - - else: - _parachute_ejection_signal_trigger[chute.name] = ( - "Ejection signal trigger: " + chute.trigger.__name__ - ) - _parachute_ejection_system_refresh_rate[chute.name] = ( - "Ejection system refresh rate: {chute.sampling_rate:.3f} Hz" - ) - _parachute_lag[chute.name] = ( - "Time between ejection signal is triggered and the parachute is fully opened: {chute.lag:.1f} s\n" - ) - - _parachute_data = ParachuteData( - parachute_details=_parachute_details, - # parachute_ejection_signal_trigger = _parachute_ejection_signal_trigger, - parachute_ejection_system_refresh_rate=_parachute_ejection_system_refresh_rate, - parachute_lag=_parachute_lag, - ) - - _rocket_data = RocketData( - inertia_details=_inertia_details, - rocket_geometrical_parameters=_rocket_geometrical_parameters, - rocket_aerodynamics_quantities=_rocket_aerodynamics_quantities, - parachute_data=_parachute_data, - ) - - rocket_summary = RocketSummary(rocket_data=_rocket_data) + rocketpy_rocket = RocketService.from_rocket_model(read_rocket) + rocket_summary = rocketpy_rocket.get_rocket_summary() except HTTPException as e: raise e from e except Exception as e: @@ -530,141 +282,3 @@ async def simulate_rocket( logger.info( f"Call to controllers.rocket.simulate completed for Rocket {rocket_id}" ) - - @staticmethod - def get_rocketpy_nose(nose: NoseCone) -> RocketPyNoseCone: - """ - Get a rocketpy nose cone object. - - Returns: - RocketPyNoseCone - """ - - rocketpy_nose = RocketPyNoseCone( - length=nose.length, - kind=nose.kind, - base_radius=nose.base_radius, - rocket_radius=nose.rocket_radius, - ) - rocketpy_nose.position = nose.position - return rocketpy_nose - - @staticmethod - def get_rocketpy_finset( - trapezoidal_fins: TrapezoidalFins, - ) -> RocketPyTrapezoidalFins: - """ - Get a rocketpy finset object. - - Returns: - RocketPyTrapezoidalFins - """ - rocketpy_finset = RocketPyTrapezoidalFins( - n=trapezoidal_fins.n, - root_chord=trapezoidal_fins.root_chord, - tip_chord=trapezoidal_fins.tip_chord, - span=trapezoidal_fins.span, - cant_angle=trapezoidal_fins.cant_angle, - rocket_radius=trapezoidal_fins.radius, - airfoil=trapezoidal_fins.airfoil, - ) - rocketpy_finset.position = trapezoidal_fins.position - return rocketpy_finset - - @staticmethod - def get_rocketpy_tail(tail: Tail) -> RocketPyTail: - """ - Get a rocketpy tail object. - - Returns: - RocketPyTail - """ - rocketpy_tail = RocketPyTail( - top_radius=tail.top_radius, - bottom_radius=tail.bottom_radius, - length=tail.length, - rocket_radius=tail.radius, - ) - rocketpy_tail.position = tail.position - return rocketpy_tail - - @staticmethod - def get_rocketpy_parachute( - parachute: Parachute, p: int - ) -> RocketPyParachute: - """ - Get a rocketpy parachute object. - - Returns: - RocketPyParachute - """ - rocketpy_parachute = RocketPyParachute( - name=parachute[p].name[0], - cd_s=parachute[p].cd_s[0], - trigger=eval(parachute[p].triggers[0]), - sampling_rate=parachute[p].sampling_rate[0], - lag=parachute[p].lag[0], - noise=parachute[p].noise[0], - ) - return rocketpy_parachute - - @staticmethod - def check_parachute_trigger(expression: str) -> bool: - """ - Check if the trigger expression is valid. - - Args: - expression: str - - Returns: - bool: True if the expression is valid, False otherwise. - """ - - # Parsing the expression into an AST - try: - parsed_expression = ast.parse(expression, mode="eval") - except SyntaxError: - print("Invalid syntax.") - return False - - # Constant case (supported after beta v1) - if isinstance(parsed_expression.body, ast.Constant): - return True - # Name case (supported after beta v1) - if ( - isinstance(parsed_expression.body, ast.Name) - and parsed_expression.body.id == "apogee" - ): - global apogee - apogee = "apogee" - return True - - # Validating the expression structure - if not isinstance(parsed_expression.body, ast.Lambda): - print("Invalid expression structure (not a Lambda).") - return False - - lambda_node = parsed_expression.body - if len(lambda_node.args.args) != 3: - print("Invalid expression structure (invalid arity).") - return False - - if not isinstance(lambda_node.body, ast.Compare): - try: - for operand in lambda_node.body.values: - if not isinstance(operand, ast.Compare): - print("Invalid expression structure (not a Compare).") - return False - except AttributeError: - print("Invalid expression structure (not a Compare).") - return False - - # Restricting access to functions or attributes - for node in ast.walk(lambda_node): - if isinstance(node, ast.Call): - print("Calling functions is not allowed in the expression.") - return False - if isinstance(node, ast.Attribute): - print("Accessing attributes is not allowed in the expression.") - return False - return True diff --git a/lib/models/aerosurfaces.py b/lib/models/aerosurfaces.py index 0dff121..1a88fbc 100644 --- a/lib/models/aerosurfaces.py +++ b/lib/models/aerosurfaces.py @@ -2,38 +2,38 @@ from pydantic import BaseModel -class RailButtons(BaseModel, frozen=True): - upper_button_position: Optional[float] = -0.5 - lower_button_position: Optional[float] = 0.2 - angular_position: Optional[float] = 45 +class RailButtons(BaseModel): + upper_button_position: float + lower_button_position: float + angular_position: float -class NoseCone(BaseModel, frozen=True): - length: float = 0.55829 - kind: str = "vonKarman" - position: float = 1.278 - base_radius: float = 0.0635 - rocket_radius: float = 0.0635 +class NoseCone(BaseModel): + length: float + kind: str + position: float + base_radius: float + rocket_radius: float -class Fins(BaseModel, frozen=True): - n: int = 4 - root_chord: float = 0.12 - tip_chord: float = 0.04 - span: float = 0.1 - position: float = -1.04956 - cant_angle: float = 0 - radius: float = 0.0635 - airfoil: str = "" +class Fins(BaseModel): + n: int + root_chord: float + tip_chord: float + span: float + position: float + cant_angle: float + radius: float + airfoil: str -class TrapezoidalFins(Fins, frozen=True): +class TrapezoidalFins(Fins): pass -class Tail(BaseModel, frozen=True): - top_radius: float = 0.0635 - bottom_radius: float = 0.0435 - length: float = 0.06 - position: float = -1.194656 - radius: float = 0.0635 +class Tail(BaseModel): + top_radius: float + bottom_radius: float + length: float + position: float + radius: float diff --git a/lib/models/environment.py b/lib/models/environment.py index 4074fde..07c08c6 100644 --- a/lib/models/environment.py +++ b/lib/models/environment.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -class Env(BaseModel, frozen=True): +class Env(BaseModel): latitude: float = 0 longitude: float = 0 elevation: Optional[int] = 1400 @@ -14,7 +14,3 @@ class Env(BaseModel, frozen=True): date: Optional[datetime.datetime] = ( datetime.datetime.today() + datetime.timedelta(days=1) ) - - @property - def env_id(self) -> str: - return str(hash(self)) diff --git a/lib/models/flight.py b/lib/models/flight.py index ab945ec..0930a4f 100644 --- a/lib/models/flight.py +++ b/lib/models/flight.py @@ -3,13 +3,9 @@ from lib.models.environment import Env -class Flight(BaseModel, frozen=True): +class Flight(BaseModel): environment: Env = Env() rocket: Rocket = Rocket() inclination: int = 85 heading: int = 0 rail_length: float = 5.2 - - @property - def flight_id(self) -> str: - return str(hash(self)) diff --git a/lib/models/motor.py b/lib/models/motor.py index 1598b49..c78b101 100644 --- a/lib/models/motor.py +++ b/lib/models/motor.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional, Tuple, List +from typing import Optional, Tuple, List, Union from rocketpy import ( LevelBasedTank, MassBasedTank, @@ -28,37 +28,41 @@ class TankKinds(str, Enum): ULLAGE: str = "ULLAGE" -class TankFluids(BaseModel, frozen=True): - name: str = "FLUIDNAME" - density: float = 100.0 +class TankFluids(BaseModel): + name: str + density: float -class MotorTank(BaseModel, frozen=True): +class MotorTank(BaseModel): # Required parameters - geometry: "List[Tuple[Tuple[float,float],float]]" = [ - ((0, 5), 1), - ((5, 10), 2), + geometry: List[Tuple[Tuple[float, float], float]] = [ + ((0.0, 5.0), 1.0), + ((5.0, 10.0), 2.0), ] tank_kind: TankKinds = TankKinds.MASS_FLOW - gas: TankFluids = TankFluids() - liquid: TankFluids = TankFluids() + gas: TankFluids = TankFluids(name="GAS", density=100) + liquid: TankFluids = TankFluids(name="LIQUID", density=1000) name: str = "Tank" - flux_time: "List[float]" = [0, 8] + flux_time: Tuple[float, float] = (0.0, 3.9) position: float = 1.0 + discretize: int = 100 # 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_in: Optional[float] = 0.0 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 + liquid_mass_flow_rate_in: Optional[float] = 0.0 + liquid_mass_flow_rate_out: Optional[float] = 1 initial_liquid_mass: Optional[float] = 5.0 - initial_gas_mass: Optional[float] = 0.1 + initial_gas_mass: Optional[float] = 0.4 ullage: Optional[float] = 0.1 + _tank: Union[ + LevelBasedTank, MassBasedTank, MassFlowRateBasedTank, UllageBasedTank + ] = PrivateAttr() + def __init__(self, **kwargs): super().__init__(**kwargs) tank_core = { @@ -93,28 +97,25 @@ def __init__(self, **kwargs): ) 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) + self._tank = tank + @property + def tank(self): + return self._tank -class Motor(BaseModel, frozen=True): - # TODO: 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 +class Motor(BaseModel): # Required parameters thrust_source: MotorEngines = MotorEngines.CESARONI_M1670 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) + dry_inertia: Tuple[float, float, float] = (0.125, 0.125, 0.002) center_of_dry_mass_position: float = 0.317 - _motor_kind: MotorKinds = PrivateAttr() + _motor_kind: MotorKinds = PrivateAttr(default=MotorKinds.SOLID) # Optional parameters - tanks: Optional["List[MotorTank]"] = [MotorTank()] + tanks: Optional[List[MotorTank]] = [MotorTank()] grain_number: Optional[int] = 5 grain_density: Optional[float] = 1815 grain_outer_radius: Optional[float] = 0.033 @@ -128,19 +129,9 @@ class Motor(BaseModel, frozen=True): "nozzle_to_combustion_chamber" ) - def __init__(self, motor_kind=MotorKinds.SOLID, **kwargs): - super().__init__(**kwargs) - self._motor_kind = motor_kind - @property def motor_kind(self) -> MotorKinds: return self._motor_kind - @property - def motor_id(self) -> str: - return str(hash(self)) - - def __hash__(self): - temp = vars(self) - temp = str(temp) - return hash(temp) + def set_motor_kind(self, motor_kind: MotorKinds): + self._motor_kind = motor_kind diff --git a/lib/models/parachute.py b/lib/models/parachute.py index 12bf480..42cc13d 100644 --- a/lib/models/parachute.py +++ b/lib/models/parachute.py @@ -2,29 +2,17 @@ from pydantic import BaseModel -class Parachute(BaseModel, frozen=True): - name: "List[str]" = ["Main", "Drogue"] - cd_s: "List[float]" = [10, 1] - 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]" = [ +class Parachute(BaseModel): + name: List[str] = ["Main", "Drogue"] + cd_s: List[float] = [10, 1] + 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", ] - def __hash__(self): - return hash( - ( - tuple(self.name), - tuple(self.cd_s), - tuple(self.sampling_rate), - tuple(self.lag), - tuple(self.noise), - tuple(self.triggers), - ) - ) - def __getitem__(self, idx): if isinstance(idx, slice): return [self[i] for i in range(*idx.indices(len(self)))] diff --git a/lib/models/rocket.py b/lib/models/rocket.py index b78e1b6..60d2173 100644 --- a/lib/models/rocket.py +++ b/lib/models/rocket.py @@ -11,48 +11,63 @@ class RocketOptions(str, Enum): CUSTOM: str = "CUSTOM" -class Rocket(BaseModel, frozen=True): +class Rocket(BaseModel): # 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]]" = [ + parachutes: Parachute = Parachute() + center_of_mass_without_motor: int = 0 + inertia: Tuple[float, float, float] = (6.321, 6.321, 0.0346) + rail_buttons: RailButtons = RailButtons( + upper_button_position=-0.5, + lower_button_position=0.2, + angular_position=45, + ) + nose: NoseCone = NoseCone( + length=0.55829, + kind="vonKarman", + position=1.278, + base_radius=0.0635, + rocket_radius=0.0635, + ) + fins: Fins = Fins( + n=4, + root_chord=0.12, + tip_chord=0.04, + span=0.1, + position=-1.04956, + cant_angle=0, + radius=0.0635, + airfoil="", + ) + tail: Tail = Tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.06, + position=-1.194656, + radius=0.0635, + ) + _rocket_option: RocketOptions = PrivateAttr(default=RocketOptions.CALISTO) + + # Optional parameters + # TODO: implement field validation so a list of possible tailToNose values is provided in the api docs + power_off_drag: Optional[List[Tuple[float, float]]] = [ (0.01, 0.333865758), (0.02, 0.394981721), (0.03, 0.407756063), ] - power_on_drag: "List[Tuple[float, float]]" = [ + power_on_drag: Optional[List[Tuple[float, float]]] = [ (0.01, 0.333865758), (0.02, 0.394981721), (0.03, 0.407756063), ] - _rocket_option: RocketOptions = PrivateAttr() - - # Optional parameters - # TODO: implement field validation so a list of possible tailToNose values is 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 - @property def rocket_option(self) -> RocketOptions: return self._rocket_option - @property - def rocket_id(self) -> str: - return str(hash(self)) - - def __hash__(self): - temp = vars(self) - temp = str(temp) - return hash(temp) + def set_rocket_option(self, rocket_option: RocketOptions): + self._rocket_option = rocket_option diff --git a/lib/repositories/environment.py b/lib/repositories/environment.py index 351b9c6..5cc3263 100644 --- a/lib/repositories/environment.py +++ b/lib/repositories/environment.py @@ -1,4 +1,5 @@ from typing import Union +from bson import ObjectId from pymongo.errors import PyMongoError from lib.models.environment import Env from lib import logger @@ -11,20 +12,13 @@ class EnvRepository(Repository): Init Attributes: environment: models.Env - env_id: str - """ - def __init__(self): + def __init__(self, environment: Env = None): super().__init__("environments") - self._env = None + self._env = environment self._env_id = None - def fetch_env(self, environment: Env): - self.env = environment - self.env_id = environment.env_id - return self - @property def env(self) -> Env: return self._env @@ -35,7 +29,7 @@ def env(self, environment: "Env"): @property def env_id(self) -> str: - return self._env_id + return str(self._env_id) @env_id.setter def env_id(self, env_id: "str"): @@ -43,12 +37,20 @@ def env_id(self, env_id: "str"): async def insert_env(self, env_data: dict): collection = self.get_collection() - await collection.insert_one(env_data) + result = await collection.insert_one(env_data) + self.env_id = result.inserted_id + return self + + async def update_env(self, env_data: dict, env_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(env_id)}, {"$set": env_data} + ) return self async def find_env(self, env_id: str): collection = self.get_collection() - return await collection.find_one({"env_id": env_id}) + return await collection.find_one({"_id": ObjectId(env_id)}) async def delete_env(self, env_id: str): collection = self.get_collection() @@ -57,14 +59,13 @@ async def delete_env(self, env_id: str): async def create_env(self): """ - Creates a non-existing models.Env in the database + Creates a models.Env in the database Returns: self """ try: environment_to_dict = self.env.dict() - environment_to_dict["env_id"] = self.env_id await self.insert_env(environment_to_dict) except PyMongoError as e: raise e from e @@ -118,3 +119,24 @@ async def delete_env_by_id(self, env_id: str): logger.info( f"Call to repositories.environment.delete_env completed for Env {env_id}" ) + + async def update_env_by_id(self, env_id: str): + """ + Updates a models.Env in the database + + Returns: + self + """ + try: + environment_to_dict = self.env.dict() + await self.update_env(environment_to_dict, env_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.environment.update_env_by_id completed for Env {env_id}" + ) diff --git a/lib/repositories/flight.py b/lib/repositories/flight.py index ac68549..edc4cd9 100644 --- a/lib/repositories/flight.py +++ b/lib/repositories/flight.py @@ -1,7 +1,10 @@ from typing import Union +from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger from lib.models.flight import Flight +from lib.models.rocket import RocketOptions +from lib.models.motor import MotorKinds from lib.repositories.repo import Repository, RepositoryNotInitializedException @@ -11,20 +14,13 @@ class FlightRepository(Repository): Init Attributes: flight: models.Flight - flight_id: str - """ - def __init__(self): + def __init__(self, flight: Flight = None): super().__init__("flights") - self._flight = None + self._flight = flight self._flight_id = None - def fetch_flight(self, flight: Flight): - self.flight = flight - self.flight_id = flight.flight_id - return self - @property def flight(self) -> Flight: return self._flight @@ -35,7 +31,7 @@ def flight(self, flight: "Flight"): @property def flight_id(self) -> str: - return self._flight_id + return str(self._flight_id) @flight_id.setter def flight_id(self, flight_id: "str"): @@ -43,35 +39,55 @@ def flight_id(self, flight_id: "str"): async def insert_flight(self, flight_data: dict): collection = self.get_collection() - await collection.insert_one(flight_data) + result = await collection.insert_one(flight_data) + self.flight_id = result.inserted_id + return self + + async def update_flight(self, flight_data: dict, flight_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(flight_id)}, {"$set": flight_data} + ) + return self + + async def update_env(self, env_data: dict, flight_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(flight_id)}, {"$set": {"environment": env_data}} + ) + return self + + async def update_rocket(self, rocket_data: dict, flight_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(flight_id)}, {"$set": {"rocket": rocket_data}} + ) + return self async def find_flight(self, flight_id: str): collection = self.get_collection() - return await collection.find_one({"flight_id": flight_id}) + return await collection.find_one({"_id": ObjectId(flight_id)}) async def delete_flight(self, flight_id: str): collection = self.get_collection() await collection.delete_one({"flight_id": flight_id}) return self - async def create_flight( - self, *, motor_kind: str = "SOLID", rocket_option: str = "CALISTO" - ): + async def create_flight(self): """ - Creates a non-existing models.Flight in the database - - Args: - rocket_option: models.rocket.RocketOptions - motor_kind: models.motor.MotorKinds + Creates a models.Flight in the database Returns: self """ 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 + flight_to_dict["rocket"][ + "rocket_option" + ] = self.flight.rocket.rocket_option.value + flight_to_dict["rocket"]["motor"][ + "motor_kind" + ] = self.flight.rocket.motor.motor_kind.value await self.insert_flight(flight_to_dict) except PyMongoError as e: raise e from e @@ -96,6 +112,12 @@ async def get_flight_by_id(self, flight_id: str) -> Union[Flight, None]: parsed_flight = ( Flight.parse_obj(read_flight) if read_flight else None ) + parsed_flight.rocket.motor.set_motor_kind( + MotorKinds(read_flight["rocket"]["motor"]["motor_kind"]) + ) + parsed_flight.rocket.set_rocket_option( + RocketOptions(read_flight["rocket"]["rocket_option"]) + ) self.flight = parsed_flight except PyMongoError as e: raise e from e @@ -127,3 +149,78 @@ async def delete_flight_by_id(self, flight_id: str): logger.info( f"Call to repositories.flight.delete_flight completed for Flight {flight_id}" ) + + async def update_flight_by_id(self, flight_id: str): + """ + Updates a models.Flight in the database + + Returns: + self + """ + try: + flight_to_dict = self.flight.dict() + flight_to_dict["rocket"][ + "rocket_option" + ] = self.flight.rocket.rocket_option.value + flight_to_dict["rocket"]["motor"][ + "motor_kind" + ] = self.flight.rocket.motor.motor_kind.value + await self.update_flight(flight_to_dict, flight_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.flight.update_flight_by_id completed for Flight {flight_id}" + ) + + async def update_env_by_flight_id(self, flight_id: str): + """ + Updates a models.Flight.Env in the database + + Returns: + self + """ + try: + env_to_dict = self.flight.env.dict() + await self.update_env(env_to_dict, flight_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.flight.update_env_by_flight_id completed for Flight {flight_id}" + ) + + async def update_rocket_by_flight_id(self, flight_id: str): + """ + Updates a models.Flight.Rocket in the database + + Returns: + self + """ + try: + rocket_to_dict = self.flight.rocket.dict() + rocket_to_dict["rocket_option"] = ( + self.flight.rocket.rocket_option.value + ) + rocket_to_dict["motor"][ + "motor_kind" + ] = self.flight.rocket.motor.motor_kind.value + await self.update_rocket(rocket_to_dict, flight_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.flight.update_rocket_by_flight_id completed for Flight {flight_id}" + ) diff --git a/lib/repositories/motor.py b/lib/repositories/motor.py index 806016a..c414654 100644 --- a/lib/repositories/motor.py +++ b/lib/repositories/motor.py @@ -1,7 +1,8 @@ from typing import Union +from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger -from lib.models.motor import Motor +from lib.models.motor import Motor, MotorKinds from lib.repositories.repo import Repository, RepositoryNotInitializedException @@ -11,20 +12,13 @@ class MotorRepository(Repository): Init Attributes: motor: models.Motor - motor_id: str - """ - def __init__(self): + def __init__(self, motor: Motor = None): super().__init__("motors") - self._motor = None + self._motor = motor self._motor_id = None - def fetch_motor(self, motor: Motor): - self.motor = motor - self.motor_id = motor.motor_id - return self - @property def motor(self) -> Motor: return self._motor @@ -35,7 +29,7 @@ def motor(self, motor: "Motor"): @property def motor_id(self) -> str: - return self._motor_id + return str(self._motor_id) @motor_id.setter def motor_id(self, motor_id: "str"): @@ -43,32 +37,36 @@ def motor_id(self, motor_id: "str"): async def insert_motor(self, motor_data: dict): collection = self.get_collection() - await collection.insert_one(motor_data) + result = await collection.insert_one(motor_data) + self.motor_id = result.inserted_id + return self + + async def update_motor(self, motor_data: dict, motor_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(motor_id)}, {"$set": motor_data} + ) return self async def find_motor(self, motor_id: str): collection = self.get_collection() - return await collection.find_one({"motor_id": motor_id}) + return await collection.find_one({"_id": ObjectId(motor_id)}) async def delete_motor(self, motor_id: str): collection = self.get_collection() await collection.delete_one({"motor_id": motor_id}) return self - async def create_motor(self, motor_kind: str = "SOLID"): + async def create_motor(self): """ - Creates a non-existing models.Motor in the database - - Args: - motor_kind: models.motor.MotorKinds + Creates a models.Motor in the database Returns: self """ try: motor_to_dict = self.motor.dict() - motor_to_dict["motor_id"] = self.motor_id - motor_to_dict["motor_kind"] = motor_kind + motor_to_dict["motor_kind"] = self.motor.motor_kind.value await self.insert_motor(motor_to_dict) except PyMongoError as e: raise e from e @@ -91,6 +89,7 @@ async def get_motor_by_id(self, motor_id: str) -> Union[motor, None]: try: read_motor = await self.find_motor(motor_id) parsed_motor = Motor.parse_obj(read_motor) if read_motor else None + parsed_motor.set_motor_kind(MotorKinds(read_motor["motor_kind"])) self.motor = parsed_motor except PyMongoError as e: raise e from e @@ -122,3 +121,25 @@ async def delete_motor_by_id(self, motor_id: str): logger.info( f"Call to repositories.motor.delete_motor completed for Motor {motor_id}" ) + + async def update_motor_by_id(self, motor_id: str): + """ + Updates a models.Motor in the database + + Returns: + self + """ + try: + motor_to_dict = self.motor.dict() + motor_to_dict["motor_kind"] = self.motor.motor_kind.value + await self.update_motor(motor_to_dict, motor_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.motor.update_motor completed for Motor {motor_id}" + ) diff --git a/lib/repositories/repo.py b/lib/repositories/repo.py index 4a56810..9a9f7ae 100644 --- a/lib/repositories/repo.py +++ b/lib/repositories/repo.py @@ -1,12 +1,18 @@ import asyncio import threading -from lib import logger -from lib.secrets import Secrets + +from tenacity import ( + stop_after_attempt, + wait_fixed, + retry, +) from pydantic import BaseModel -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 +from motor.motor_asyncio import AsyncIOMotorClient +from fastapi import HTTPException, status + +from lib import logger +from lib.secrets import Secrets class RepositoryNotInitializedException(HTTPException): diff --git a/lib/repositories/rocket.py b/lib/repositories/rocket.py index 2c74cee..cf0954b 100644 --- a/lib/repositories/rocket.py +++ b/lib/repositories/rocket.py @@ -1,7 +1,9 @@ from typing import Union +from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger -from lib.models.rocket import Rocket +from lib.models.rocket import Rocket, RocketOptions +from lib.models.motor import MotorKinds from lib.repositories.repo import Repository, RepositoryNotInitializedException @@ -11,19 +13,12 @@ class RocketRepository(Repository): Init Attributes: rocket: models.Rocket - rocket_id: str - """ - def __init__(self): + def __init__(self, rocket: Rocket = None): super().__init__("rockets") self._rocket_id = None - self._rocket = None - - def fetch_rocket(self, rocket: Rocket): - self.rocket = rocket - self.rocket_id = rocket.rocket_id - return self + self._rocket = rocket @property def rocket(self) -> Rocket: @@ -35,7 +30,7 @@ def rocket(self, rocket: "Rocket"): @property def rocket_id(self) -> str: - return self._rocket_id + return str(self._rocket_id) @rocket_id.setter def rocket_id(self, rocket_id: "str"): @@ -43,36 +38,39 @@ def rocket_id(self, rocket_id: "str"): async def insert_rocket(self, rocket_data: dict): collection = self.get_collection() - await collection.insert_one(rocket_data) + result = await collection.insert_one(rocket_data) + self.rocket_id = result.inserted_id + return self + + async def update_rocket(self, rocket_data: dict, rocket_id: str): + collection = self.get_collection() + await collection.update_one( + {"_id": ObjectId(rocket_id)}, {"$set": rocket_data} + ) return self async def find_rocket(self, rocket_id: str): collection = self.get_collection() - return await collection.find_one({"rocket_id": rocket_id}) + return await collection.find_one({"_id": ObjectId(rocket_id)}) async def delete_rocket(self, rocket_id: str): collection = self.get_collection() await collection.delete_one({"rocket_id": rocket_id}) return self - async def create_rocket( - self, *, rocket_option: str = "CALISTO", motor_kind: str = "SOLID" - ): + async def create_rocket(self): """ - Creates a non-existing models.Rocket in the database - - Args: - rocket_option: models.rocket.RocketOptions - motor_kind: models.motor.MotorKinds + Creates a models.Rocket in the database Returns: self """ try: rocket_to_dict = self.rocket.dict() - rocket_to_dict["rocket_id"] = self.rocket_id - rocket_to_dict["rocket_option"] = rocket_option - rocket_to_dict["motor"]["motor_kind"] = motor_kind + rocket_to_dict["rocket_option"] = self.rocket.rocket_option.value + rocket_to_dict["motor"][ + "motor_kind" + ] = self.rocket.motor.motor_kind.value await self.insert_rocket(rocket_to_dict) except PyMongoError as e: raise e from e @@ -97,6 +95,12 @@ async def get_rocket_by_id(self, rocket_id: str) -> Union[Rocket, None]: parsed_rocket = ( Rocket.parse_obj(read_rocket) if read_rocket else None ) + parsed_rocket.motor.set_motor_kind( + MotorKinds(read_rocket["motor"]["motor_kind"]) + ) + parsed_rocket.set_rocket_option( + RocketOptions(read_rocket["rocket_option"]) + ) self.rocket = parsed_rocket except PyMongoError as e: raise e from e @@ -128,3 +132,28 @@ async def delete_rocket_by_id(self, rocket_id: str): logger.info( f"Call to repositories.rocket.delete_rocket completed for Rocket {rocket_id}" ) + + async def update_rocket_by_id(self, rocket_id: str): + """ + Updates a models.Rocket in the database + + Returns: + self + """ + try: + rocket_to_dict = self.rocket.dict() + rocket_to_dict["rocket_option"] = self.rocket.rocket_option.value + rocket_to_dict["motor"][ + "motor_kind" + ] = self.rocket.motor.motor_kind.value + await self.update_rocket(rocket_to_dict, rocket_id) + except PyMongoError as e: + raise e from e + except RepositoryNotInitializedException as e: + raise e from e + else: + return self + finally: + logger.info( + f"Call to repositories.rocket.update_rocket_by_id completed for Rocket {rocket_id}" + ) diff --git a/lib/routes/environment.py b/lib/routes/environment.py index 2417022..38840b7 100644 --- a/lib/routes/environment.py +++ b/lib/routes/environment.py @@ -9,7 +9,6 @@ EnvSummary, EnvCreated, EnvUpdated, - EnvDeleted, EnvPickle, ) from lib.models.environment import Env @@ -67,18 +66,6 @@ async def update_env(env_id: str, env: Env) -> EnvUpdated: return await EnvController(env).update_env_by_id(env_id) -@router.delete("/{env_id}") -async def delete_env(env_id: str) -> EnvDeleted: - """ - Deletes an environment - - ## Args - ``` env_id: Environment ID hash ``` - """ - with tracer.start_as_current_span("delete_env"): - return await EnvController.delete_env_by_id(env_id) - - @router.get("/rocketpy/{env_id}") async def read_rocketpy_env(env_id: str) -> EnvPickle: """ @@ -91,7 +78,7 @@ async def read_rocketpy_env(env_id: str) -> EnvPickle: return await EnvController.get_rocketpy_env_as_jsonpickle(env_id) -@router.get("/{env_id}/simulate") +@router.get("/{env_id}/simulate", include_in_schema=False) async def simulate_env(env_id: str) -> EnvSummary: """ Loads rocketpy.environment simulation diff --git a/lib/routes/flight.py b/lib/routes/flight.py index 2d597c6..adf432f 100644 --- a/lib/routes/flight.py +++ b/lib/routes/flight.py @@ -9,7 +9,6 @@ FlightSummary, FlightCreated, FlightUpdated, - FlightDeleted, FlightPickle, ) from lib.models.environment import Env @@ -42,9 +41,9 @@ async def create_flight( ``` Flight object as JSON ``` """ with tracer.start_as_current_span("create_flight"): - return await FlightController( - flight, rocket_option=rocket_option, motor_kind=motor_kind - ).create_flight() + flight.rocket.set_rocket_option(rocket_option) + flight.rocket.motor.set_motor_kind(motor_kind) + return await FlightController(flight).create_flight() @router.get("/{flight_id}") @@ -53,7 +52,7 @@ async def read_flight(flight_id: str) -> Flight: Reads a flight ## Args - ``` flight_id: Flight ID hash ``` + ``` flight_id: Flight ID ``` """ with tracer.start_as_current_span("read_flight"): return await FlightController.get_flight_by_id(flight_id) @@ -65,7 +64,7 @@ async def read_rocketpy_flight(flight_id: str) -> FlightPickle: Reads a rocketpy flight object ## Args - ``` flight_id: Flight ID hash. ``` + ``` flight_id: Flight ID ``` """ with tracer.start_as_current_span("read_rocketpy_flight"): return await FlightController.get_rocketpy_flight_as_jsonpickle( @@ -80,7 +79,7 @@ async def update_flight_env(flight_id: str, env: Env) -> FlightUpdated: ## Args ``` - flight_id: Flight ID hash + flight_id: Flight ID env: env object as JSON ``` """ @@ -102,16 +101,16 @@ async def update_flight_rocket( ## Args ``` - flight_id: Flight ID hash. + flight_id: Flight ID rocket: Rocket object as JSON ``` """ with tracer.start_as_current_span("update_flight_rocket"): + rocket.set_rocket_option(rocket_option) + rocket.motor.set_motor_kind(motor_kind) return await FlightController.update_rocket_by_flight_id( flight_id, rocket=rocket, - rocket_option=rocket_option, - motor_kind=motor_kind, ) @@ -127,35 +126,23 @@ async def update_flight( ## Args ``` - flight_id: Flight ID hash. + flight_id: Flight ID flight: Flight object as JSON ``` """ with tracer.start_as_current_span("update_flight"): - return await FlightController( - flight, rocket_option=rocket_option, motor_kind=motor_kind - ).update_flight_by_id(flight_id) + flight.rocket.set_rocket_option(rocket_option) + flight.rocket.motor.set_motor_kind(motor_kind) + return await FlightController(flight).update_flight_by_id(flight_id) -@router.delete("/{flight_id}") -async def delete_flight(flight_id: str) -> FlightDeleted: - """ - Deletes a flight - - ## Args - ``` flight_id: Flight ID hash ``` - """ - with tracer.start_as_current_span("delete_flight"): - return await FlightController.delete_flight_by_id(flight_id) - - -@router.get("/{flight_id}/simulate") +@router.get("/{flight_id}/simulate", include_in_schema=False) async def simulate_flight(flight_id: str) -> FlightSummary: """ Simulates a flight ## Args - ``` flight_id: Flight ID hash ``` + ``` flight_id: Flight ID ``` """ with tracer.start_as_current_span("simulate_flight"): return await FlightController.simulate_flight(flight_id) diff --git a/lib/routes/motor.py b/lib/routes/motor.py index 5c4cbff..faa0f21 100644 --- a/lib/routes/motor.py +++ b/lib/routes/motor.py @@ -9,7 +9,6 @@ MotorSummary, MotorCreated, MotorUpdated, - MotorDeleted, MotorPickle, ) from lib.models.motor import Motor, MotorKinds @@ -37,9 +36,8 @@ async def create_motor(motor: Motor, motor_kind: MotorKinds) -> MotorCreated: ``` Motor object as a JSON ``` """ with tracer.start_as_current_span("create_motor"): - return await MotorController( - motor=motor, motor_kind=motor_kind - ).create_motor() + motor.set_motor_kind(motor_kind) + return await MotorController(motor).create_motor() @router.get("/{motor_id}") @@ -48,7 +46,7 @@ async def read_motor(motor_id: str) -> Motor: Reads a motor ## Args - ``` motor_id: Motor ID hash ``` + ``` motor_id: Motor ID ``` """ with tracer.start_as_current_span("read_motor"): return await MotorController.get_motor_by_id(motor_id) @@ -63,26 +61,13 @@ async def update_motor( ## Args ``` - motor_id: Motor ID hash + motor_id: Motor ID motor: Motor object as JSON ``` """ with tracer.start_as_current_span("update_motor"): - return await MotorController( - motor=motor, motor_kind=motor_kind - ).update_motor_by_id(motor_id) - - -@router.delete("/{motor_id}") -async def delete_motor(motor_id: str) -> MotorDeleted: - """ - Deletes a motor - - ## Args - ``` motor_id: Motor ID hash ``` - """ - with tracer.start_as_current_span("delete_motor"): - return await MotorController.delete_motor_by_id(motor_id) + motor.set_motor_kind(motor_kind) + return await MotorController(motor).update_motor_by_id(motor_id) @router.get("/rocketpy/{motor_id}") @@ -91,19 +76,19 @@ async def read_rocketpy_motor(motor_id: str) -> MotorPickle: Reads a rocketpy motor ## Args - ``` motor_id: Motor ID hash ``` + ``` motor_id: Motor ID ``` """ with tracer.start_as_current_span("read_rocketpy_motor"): return await MotorController.get_rocketpy_motor_as_jsonpickle(motor_id) -@router.get("/{motor_id}/simulate") +@router.get("/{motor_id}/simulate", include_in_schema=False) async def simulate_motor(motor_id: str) -> MotorSummary: """ Simulates a motor ## Args - ``` motor_id: Motor ID hash ``` + ``` motor_id: Motor ID ``` """ with tracer.start_as_current_span("simulate_motor"): return await MotorController.simulate_motor(motor_id) diff --git a/lib/routes/rocket.py b/lib/routes/rocket.py index 1d7301e..48eecf8 100644 --- a/lib/routes/rocket.py +++ b/lib/routes/rocket.py @@ -9,7 +9,6 @@ RocketSummary, RocketCreated, RocketUpdated, - RocketDeleted, RocketPickle, ) from lib.models.rocket import Rocket, RocketOptions @@ -40,9 +39,9 @@ async def create_rocket( ``` Rocket object as a JSON ``` """ with tracer.start_as_current_span("create_rocket"): - return await RocketController( - rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind - ).create_rocket() + rocket.set_rocket_option(rocket_option) + rocket.motor.set_motor_kind(motor_kind) + return await RocketController(rocket).create_rocket() @router.get("/{rocket_id}") @@ -51,7 +50,7 @@ async def read_rocket(rocket_id: str) -> Rocket: Reads a rocket ## Args - ``` rocket_id: Rocket ID hash ``` + ``` rocket_id: Rocket ID ``` """ with tracer.start_as_current_span("read_rocket"): return await RocketController.get_rocket_by_id(rocket_id) @@ -69,26 +68,14 @@ async def update_rocket( ## Args ``` - rocket_id: Rocket ID hash + rocket_id: Rocket ID rocket: Rocket object as JSON ``` """ with tracer.start_as_current_span("update_rocket"): - return await RocketController( - rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind - ).update_rocket_by_id(rocket_id) - - -@router.delete("/{rocket_id}") -async def delete_rocket(rocket_id: str) -> RocketDeleted: - """ - Deletes a rocket - - ## Args - ``` rocket_id: Rocket ID hash ``` - """ - with tracer.start_as_current_span("delete_rocket"): - return await RocketController.delete_rocket_by_id(rocket_id) + rocket.set_rocket_option(rocket_option) + rocket.motor.set_motor_kind(motor_kind) + return await RocketController(rocket).update_rocket_by_id(rocket_id) @router.get("/rocketpy/{rocket_id}") @@ -97,7 +84,7 @@ async def read_rocketpy_rocket(rocket_id: str) -> RocketPickle: Reads a rocketpy rocket ## Args - ``` rocket_id: Rocket ID hash ``` + ``` rocket_id: Rocket ID ``` """ with tracer.start_as_current_span("read_rocketpy_rocket"): return await RocketController.get_rocketpy_rocket_as_jsonpickle( @@ -105,13 +92,13 @@ async def read_rocketpy_rocket(rocket_id: str) -> RocketPickle: ) -@router.get("/{rocket_id}/simulate") +@router.get("/{rocket_id}/simulate", include_in_schema=False) async def simulate_rocket(rocket_id: str) -> RocketSummary: """ Simulates a rocket ## Args - ``` rocket_id: Rocket ID hash ``` + ``` rocket_id: Rocket ID ``` """ with tracer.start_as_current_span("simulate_rocket"): return await RocketController.simulate_rocket(rocket_id) diff --git a/lib/services/flight.py b/lib/services/flight.py new file mode 100644 index 0000000..15233f8 --- /dev/null +++ b/lib/services/flight.py @@ -0,0 +1,42 @@ +from typing import Self + +from rocketpy.simulation.flight import Flight as RocketPyFlight +from rocketpy.utilities import get_instance_attributes + +from lib.models.flight import Flight +from lib.services.environment import EnvironmentService +from lib.services.rocket import RocketService +from lib.views.flight import FlightSummary + + +class FlightService(RocketPyFlight): + + @classmethod + def from_flight_model(cls, flight: Flight) -> Self: + """ + Get the rocketpy flight object. + + Returns: + RocketPyFlight + """ + rocketpy_rocket = RocketService.from_rocket_model(flight.rocket) + rocketpy_env = EnvironmentService.from_env_model(flight.environment) + rocketpy_flight = RocketPyFlight( + rocket=rocketpy_rocket, + inclination=flight.inclination, + heading=flight.heading, + environment=rocketpy_env, + rail_length=flight.rail_length, + ) + return rocketpy_flight + + def get_flight_summary(self) -> FlightSummary: + """ + Get the summary of the flight. + + Returns: + FlightSummary + """ + attributes = get_instance_attributes(self) + flight_summary = FlightSummary(**attributes) + return flight_summary diff --git a/lib/services/motor.py b/lib/services/motor.py new file mode 100644 index 0000000..ee60a8e --- /dev/null +++ b/lib/services/motor.py @@ -0,0 +1,82 @@ +from typing import Self +from rocketpy.motors.solid_motor import SolidMotor +from rocketpy.motors.liquid_motor import LiquidMotor +from rocketpy.motors.hybrid_motor import HybridMotor +from rocketpy.utilities import get_instance_attributes +from lib.models.motor import Motor, MotorKinds +from lib.views.motor import MotorSummary + + +class MotorService: + + @classmethod + def from_motor_model(cls, motor: Motor) -> Self: + """ + Get the rocketpy motor object. + + Returns: + Mixin of rocketpy motor and MotorService + """ + + motor_core = { + "thrust_source": ( + f"lib/data/engines/{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.motor_kind: + case MotorKinds.LIQUID: + rocketpy_motor = type( + "LiquidMotorMixin", (LiquidMotor, cls), {} + )(**motor_core) + case MotorKinds.HYBRID: + rocketpy_motor = type( + "HybridMotorMixin", (HybridMotor, cls), {} + )( + **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 = type( + "SolidMotorMixin", (SolidMotor, cls), {} + )( + **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.motor_kind != MotorKinds.SOLID: + for tank in motor.tanks: + rocketpy_motor.add_tank(tank.tank, tank.position) + + return rocketpy_motor + + def get_motor_summary(self) -> MotorSummary: + """ + Get the summary of the motor. + + Returns: + MotorSummary + """ + attributes = get_instance_attributes(self) + motor_summary = MotorSummary(**attributes) + return motor_summary diff --git a/lib/services/rocket.py b/lib/services/rocket.py new file mode 100644 index 0000000..d3cd527 --- /dev/null +++ b/lib/services/rocket.py @@ -0,0 +1,230 @@ +import ast +from typing import Self +from rocketpy.rocket.rocket import Rocket as RocketPyRocket +from rocketpy.rocket.parachute import Parachute as RocketPyParachute +from rocketpy.rocket.aero_surface import NoseCone as RocketPyNoseCone +from rocketpy.rocket.aero_surface import ( + TrapezoidalFins as RocketPyTrapezoidalFins, +) +from rocketpy.rocket.aero_surface import Tail as RocketPyTail +from rocketpy.utilities import get_instance_attributes + +from lib.models.rocket import Rocket +from lib.models.aerosurfaces import NoseCone, TrapezoidalFins, Tail +from lib.models.parachute import Parachute +from lib.services.motor import MotorService +from lib.views.rocket import RocketSummary + + +class RocketService(RocketPyRocket): + + @classmethod + def from_rocket_model(cls, rocket: Rocket) -> Self: + """ + Get the rocketpy rocket object. + + Returns: + RocketPyRocket + """ + + rocketpy_rocket = RocketPyRocket( + radius=rocket.radius, + mass=rocket.mass, + inertia=rocket.inertia, + power_off_drag=rocket.power_off_drag, + power_on_drag=rocket.power_on_drag, + center_of_mass_without_motor=rocket.center_of_mass_without_motor, + coordinate_system_orientation=rocket.coordinate_system_orientation, + ) + + # RailButtons + 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( + MotorService.from_motor_model(rocket.motor), + rocket.motor_position, + ) + + # NoseCone + nose = cls.get_rocketpy_nose(rocket.nose) + rocketpy_rocket.aerodynamic_surfaces.add(nose, nose.position) + rocketpy_rocket.evaluate_static_margin() + + # FinSet + # TODO: re-write this to match overall fins not only TrapezoidalFins + # Maybe a strategy with different factory methods? + finset = cls.get_rocketpy_finset(rocket.fins) + rocketpy_rocket.aerodynamic_surfaces.add(finset, finset.position) + rocketpy_rocket.evaluate_static_margin() + + # Tail + tail = cls.get_rocketpy_tail(rocket.tail) + rocketpy_rocket.aerodynamic_surfaces.add(tail, tail.position) + rocketpy_rocket.evaluate_static_margin() + + # Parachutes + for p, _ in enumerate(rocket.parachutes): + parachute_trigger = rocket.parachutes[p].triggers[0] + if cls.check_parachute_trigger(parachute_trigger): + rocket.parachutes[p].triggers[0] = compile( + parachute_trigger, "", "eval" + ) + parachute = cls.get_rocketpy_parachute(rocket.parachutes, p) + rocketpy_rocket.parachutes.append(parachute) + else: + print("Parachute trigger not valid. Skipping parachute.") + continue + + return rocketpy_rocket + + def get_rocket_summary(self) -> RocketSummary: + """ + Get the summary of the rocket. + + Returns: + RocketSummary + """ + attributes = get_instance_attributes(self) + rocket_summary = RocketSummary(**attributes) + return rocket_summary + + @staticmethod + def get_rocketpy_nose(nose: NoseCone) -> RocketPyNoseCone: + """ + Get a rocketpy nose cone object. + + Returns: + RocketPyNoseCone + """ + + rocketpy_nose = RocketPyNoseCone( + length=nose.length, + kind=nose.kind, + base_radius=nose.base_radius, + rocket_radius=nose.rocket_radius, + ) + rocketpy_nose.position = nose.position + return rocketpy_nose + + @staticmethod + def get_rocketpy_finset( + trapezoidal_fins: TrapezoidalFins, + ) -> RocketPyTrapezoidalFins: + """ + Get a rocketpy finset object. + + Returns: + RocketPyTrapezoidalFins + """ + rocketpy_finset = RocketPyTrapezoidalFins( + n=trapezoidal_fins.n, + root_chord=trapezoidal_fins.root_chord, + tip_chord=trapezoidal_fins.tip_chord, + span=trapezoidal_fins.span, + cant_angle=trapezoidal_fins.cant_angle, + rocket_radius=trapezoidal_fins.radius, + airfoil=trapezoidal_fins.airfoil, + ) + rocketpy_finset.position = trapezoidal_fins.position + return rocketpy_finset + + @staticmethod + def get_rocketpy_tail(tail: Tail) -> RocketPyTail: + """ + Get a rocketpy tail object. + + Returns: + RocketPyTail + """ + rocketpy_tail = RocketPyTail( + top_radius=tail.top_radius, + bottom_radius=tail.bottom_radius, + length=tail.length, + rocket_radius=tail.radius, + ) + rocketpy_tail.position = tail.position + return rocketpy_tail + + @staticmethod + def get_rocketpy_parachute( + parachute: Parachute, p: int + ) -> RocketPyParachute: + """ + Get a rocketpy parachute object. + + Returns: + RocketPyParachute + """ + rocketpy_parachute = RocketPyParachute( + name=parachute[p].name[0], + cd_s=parachute[p].cd_s[0], + trigger=eval(parachute[p].triggers[0]), + sampling_rate=parachute[p].sampling_rate[0], + lag=parachute[p].lag[0], + noise=parachute[p].noise[0], + ) + return rocketpy_parachute + + @staticmethod + def check_parachute_trigger(expression: str) -> bool: + """ + Check if the trigger expression is valid. + + Args: + expression: str + + Returns: + bool: True if the expression is valid, False otherwise. + """ + + # Parsing the expression into an AST + try: + parsed_expression = ast.parse(expression, mode="eval") + except SyntaxError: + print("Invalid syntax.") + return False + + # Constant case (supported after beta v1) + if isinstance(parsed_expression.body, ast.Constant): + return True + # Name case (supported after beta v1) + if ( + isinstance(parsed_expression.body, ast.Name) + and parsed_expression.body.id == "apogee" + ): + global apogee + apogee = "apogee" + return True + + # Validating the expression structure + if not isinstance(parsed_expression.body, ast.Lambda): + print("Invalid expression structure (not a Lambda).") + return False + + lambda_node = parsed_expression.body + if len(lambda_node.args.args) != 3: + print("Invalid expression structure (invalid arity).") + return False + + if not isinstance(lambda_node.body, ast.Compare): + try: + for operand in lambda_node.body.values: + if not isinstance(operand, ast.Compare): + print("Invalid expression structure (not a Compare).") + return False + except AttributeError: + print("Invalid expression structure (not a Compare).") + return False + + # Restricting access to functions or attributes + for node in ast.walk(lambda_node): + if isinstance(node, ast.Call): + print("Calling functions is not allowed in the expression.") + return False + if isinstance(node, ast.Attribute): + print("Accessing attributes is not allowed in the expression.") + return False + return True diff --git a/lib/views/environment.py b/lib/views/environment.py index 15157db..b26b115 100644 --- a/lib/views/environment.py +++ b/lib/views/environment.py @@ -1,4 +1,3 @@ -from typing import List from pydantic import BaseModel @@ -53,12 +52,12 @@ class EnvCreated(BaseModel): class EnvUpdated(BaseModel): - new_env_id: str + env_id: str message: str = "Environment successfully updated" class EnvDeleted(BaseModel): - deleted_env_id: str + env_id: str message: str = "Environment successfully deleted" diff --git a/lib/views/flight.py b/lib/views/flight.py index 3d64591..ace8b00 100644 --- a/lib/views/flight.py +++ b/lib/views/flight.py @@ -112,12 +112,12 @@ class FlightCreated(BaseModel): class FlightUpdated(BaseModel): - new_flight_id: str + flight_id: str message: str = "Flight successfully updated" class FlightDeleted(BaseModel): - deleted_flight_id: str + flight_id: str message: str = "Flight successfully deleted" diff --git a/lib/views/motor.py b/lib/views/motor.py index 0be590e..74cf6b2 100644 --- a/lib/views/motor.py +++ b/lib/views/motor.py @@ -2,16 +2,13 @@ from pydantic import BaseModel -class MotorData(BaseModel): +class MotorSummary(BaseModel): total_burning_time: str total_propellant_mass: str average_propellant_exhaust_velocity: str average_thrust: str maximum_thrust: str total_impulse: str - - -class MotorPlots(BaseModel): thrust: List[Any] total_mass: List[Any] center_of_mass: List[Any] @@ -23,23 +20,18 @@ class MotorPlots(BaseModel): i_23: List[Any] -class MotorSummary(BaseModel): - motor_data: MotorData - # motor_plots: MotorPlots - - class MotorCreated(BaseModel): motor_id: str message: str = "Motor successfully created" class MotorUpdated(BaseModel): - new_motor_id: str + motor_id: str message: str = "Motor successfully updated" class MotorDeleted(BaseModel): - deleted_motor_id: str + motor_id: str message: str = "Motor successfully deleted" diff --git a/lib/views/rocket.py b/lib/views/rocket.py index 07ee848..2fbf191 100644 --- a/lib/views/rocket.py +++ b/lib/views/rocket.py @@ -53,12 +53,12 @@ class RocketCreated(BaseModel): class RocketUpdated(BaseModel): - new_rocket_id: str + rocket_id: str message: str = "Rocket successfully updated" class RocketDeleted(BaseModel): - deleted_rocket_id: str + rocket_id: str message: str = "Rocket successfully deleted" diff --git a/pyproject.toml b/pyproject.toml index 190673c..532a6f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ dependencies = {file = ["requirements.txt"]} [project] name = "Infinity-API" -version = "1.2.2" +version = "2.0.0" description = "RESTFULL open API for rocketpy" dynamic = ["dependencies"] requires-python = ">=3.12" @@ -55,5 +55,7 @@ disable = """ wrong-import-position, consider-using-f-string, too-many-function-args, - no-member + no-member, + attribute-defined-outside-init, + no-else-raise """ diff --git a/tests/Infinity-API.postman_collection.json b/tests/Infinity-API.postman_collection.json deleted file mode 100644 index 4ff23fc..0000000 --- a/tests/Infinity-API.postman_collection.json +++ /dev/null @@ -1,4304 +0,0 @@ -{ - "info": { - "_postman_id": "00c970b2-c429-4ecd-a1f7-de23aa286d10", - "name": "Infinity-API", - "description": "# About this collection\n\nThe API under this collection includes four artifacts **{Environment, Flight, Motor and Rocket}** with 6 endpoints each covering artifact **creation, reading, editing, deleting, simulating and retrieving artifact as jsonpickle string.**\n\n- POST `api/artifact/{{artifact_id}}` { message, artifact_id }\n- GET `api/artifact/{{artifact_id}}` { Artifact }\n- GET `api/rocketpy/artifact/{{artifact_id}}` { json_pickle_string_artifact }\n- GET `api/simulate/artifact/{{artifact_id}}` { ArtifactSimulationSummary }\n- PUT `api/artifact/{{artifact_id}}` { message, new_artifact_id }\n- DELETE `api/artifact/{{artifact_id}}` { deleted_artifact_id, message }\n \n\n**Flight artifact** have also additional routes that allows to update its own artifacts.\n\n- POST `api/flight/{{flight_id}}/artifact/` { message, flight_id }\n \n\n## **Using this collection**\n\n- Run this collection by clicking on \"Run\".\n \n\n\n\n## Additional resources\n\n[Scripting in Postman](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/)\n\n[Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/)\n\n[Postman Sandbox API reference](https://learning.postman.com/docs/sending-requests/grpc/postman-sandbox-api/#writing-assertions)\n\n[Using the Collection Runner](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/)", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "29631298", - "_collection_link": "https://rocketpy-team.postman.co/workspace/Team-Workspace~d228e0d7-1148-4935-8e58-7db52744ee04/collection/29631298-00c970b2-c429-4ecd-a1f7-de23aa286d10?action=share&source=collection_link&creator=29631298" - }, - "item": [ - { - "name": "Environment", - "item": [ - { - "name": "Create Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var envRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce date for future assert", - "envRequest.date = envRequest.date.substring(0, envRequest.date.length - 7);", - "", - "// save environment parameters", - "pm.environment.set('env_id', apiRspn.env_id) ", - "pm.environment.set('latitude', envRequest.latitude)", - "pm.environment.set('longitude', envRequest.longitude)", - "pm.environment.set('elevation', envRequest.elevation) ", - "pm.environment.set('atmospheric_model_type', envRequest.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', envRequest.atmospheric_model_file) ", - "pm.environment.set('date', envRequest.date) ", - "", - "//TEST", - "bdd = \"Given a valid environment POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Environment successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid env_id\", function () {", - " pm.expect(apiRspn.env_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"latitude\": 0,\n \"longitude\": 0,\n \"elevation\": 1400,\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"atmospheric_model_file\": \"GFS\",\n \"date\": \"2023-05-09T16:30:50.065992\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/environments/", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "" - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "var returned_date = apiRspn.date;", - "var reduced_returned_date = returned_date.substring(0, returned_date.length - 7);", - "", - "//TEST", - "bdd = \"Given a valid Environment GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid environment\", function () {", - " pm.expect(apiRspn.latitude).to.eql(pm.environment.get('latitude'), \"latitude not matching\");", - " pm.expect(apiRspn.longitude).to.eql(pm.environment.get('longitude'), \"longitude not matching\"); ", - " pm.expect(apiRspn.elevation).to.eql(pm.environment.get('elevation'), \"elevation not matching\");", - " pm.expect(apiRspn.atmospheric_model_type).to.eql(pm.environment.get('atmospheric_model_type'), \"atmospheric_model_type not matching\");", - " pm.expect(apiRspn.atmospheric_model_file).to.eql(pm.environment.get('atmospheric_model_file'), \"atmospheric_model_file not matching\");", - " pm.expect(reduced_returned_date).to.eql(pm.environment.get('date'), \"date not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/environments/{{env_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "{{env_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Environment GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_env).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/environments/rocketpy/{{env_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "rocketpy", - "{{env_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Environment simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.env_data).to.exist; ", - " //pm.expect(apiRspn.env_data.grav).to.exist; ", - " pm.expect(apiRspn.env_data.wind_speed).to.exist;", - " pm.expect(apiRspn.env_data.model_type_max_expected_height).to.exist;", - " pm.expect(apiRspn.env_data.wind_speed).to.exist;", - " pm.expect(apiRspn.env_data.wind_direction).to.exist;", - " pm.expect(apiRspn.env_data.wind_heading).to.exist;", - " pm.expect(apiRspn.env_data.surface_pressure).to.exist;", - " pm.expect(apiRspn.env_data.surface_temperature).to.exist;", - " pm.expect(apiRspn.env_data.surface_air_density).to.exist;", - " pm.expect(apiRspn.env_data.surface_speed_of_sound).to.exist;", - " pm.expect(apiRspn.env_data.launch_date).to.exist;", - " pm.expect(apiRspn.env_data.lat).to.eql(pm.environment.get('latitude'), \"latitude not matching\");", - " pm.expect(apiRspn.env_data.lon).to.eql(pm.environment.get('longitude'), \"longitude not matching\"); ", - " pm.expect(apiRspn.env_data.elevation).to.eql(pm.environment.get('elevation'), \"elevation not matching\");", - " pm.expect(apiRspn.env_data.model_type).to.eql(pm.environment.get('atmospheric_model_type'), \"atmospheric_model_type not matching\"); ", - "", - " pm.expect(apiRspn.env_plots).to.exist; ", - " pm.expect(apiRspn.env_plots.grid).to.exist;", - " pm.expect(apiRspn.env_plots.wind_speed).to.exist;", - " pm.expect(apiRspn.env_plots.wind_direction).to.exist;", - " pm.expect(apiRspn.env_plots.speed_of_sound).to.exist;", - " pm.expect(apiRspn.env_plots.density).to.exist;", - " pm.expect(apiRspn.env_plots.wind_vel_x).to.exist;", - " pm.expect(apiRspn.env_plots.wind_vel_y).to.exist;", - " pm.expect(apiRspn.env_plots.pressure).to.exist;", - " pm.expect(apiRspn.env_plots.temperature).to.exist;", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/environments/{{env_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "{{env_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var envRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce date for future assert", - "envRequest.date = envRequest.date.substring(0, envRequest.date.length - 7);", - "", - "// save environment parameters", - "pm.environment.set('env_id', apiRspn.new_env_id) ", - "pm.environment.set('latitude', envRequest.latitude)", - "pm.environment.set('longitude', envRequest.longitude)", - "pm.environment.set('elevation', envRequest.elevation) ", - "pm.environment.set('atmospheric_model_type', envRequest.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', envRequest.atmospheric_model_file) ", - "pm.environment.set('date', envRequest.date) ", - "", - "//TEST", - "bdd = \"Given a valid Environment PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Environment successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_env_id\", function () {", - " pm.expect(apiRspn.new_env_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"latitude\": 0,\n \"longitude\": 0,\n \"elevation\": 1400,\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"atmospheric_model_file\": \"GFS\",\n \"date\": \"2023-05-09T16:30:50.065992\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/environments/{{env_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "{{env_id}}" - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Environment DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Environment successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_env_id).to.eql(pm.environment.get('env_id'), \"env_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/environments/{{env_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "environments", - "{{env_id}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Flight", - "item": [ - { - "name": "Hybrid", - "item": [ - { - "name": "Create Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('flight_id', apiRspn.flight_id) ", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight_id\", function () {", - " pm.expect(apiRspn.flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1400,\n \"latitude\": 0,\n \"longitude\": 0\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/?rocket_option=Calisto&motor_kind=Hybrid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Hybrid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "var returned_date = apiRspn.environment.date;", - "var reduced_returned_date = returned_date.substring(0, returned_date.length - 7);", - "", - "//TEST", - "bdd = \"Given a valid Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid flight \", function () {", - " pm.expect(apiRspn.inclination).to.eql(pm.environment.get('inclination'), \"flight inclination not matching\");", - " pm.expect(apiRspn.heading).to.eql(pm.environment.get('heading'), \"flight heading not matching\");", - " pm.expect(apiRspn.rail_length).to.eql(pm.environment.get('rail_length'), \"flight rail_length not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight environment\", function () {", - " pm.expect(apiRspn.environment.longitude).to.eql(pm.environment.get('longitude'), \"environment longitude not matching\"); ", - " pm.expect(apiRspn.environment.elevation).to.eql(pm.environment.get('elevation'), \"environment elevation not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_type).to.eql(pm.environment.get('atmospheric_model_type'), \"environment atmospheric_model_type not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_file).to.eql(pm.environment.get('atmospheric_model_file'), \"environment atmospheric_model_file not matching\");", - " pm.expect(reduced_returned_date).to.eql(pm.environment.get('date'), \"date not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket\", function () { ", - " pm.expect(apiRspn.rocket.radius).to.eql(pm.environment.get('radius'), \"rocket radius not matching\");", - " pm.expect(apiRspn.rocket.mass).to.eql(pm.environment.get('mass'), \"rocket mass not matching\");", - " pm.expect(apiRspn.rocket.inertia).to.eql(pm.environment.get('inertia'), \"rocket inertia not matching\");", - " pm.expect(apiRspn.rocket.power_off_drag).to.eql(pm.environment.get('power_off_drag'), \"rocket power_off_drag not matching\");", - " pm.expect(apiRspn.rocket.power_on_drag).to.eql(pm.environment.get('power_on_drag'), \"rocket power_on_drag not matching\");", - " pm.expect(apiRspn.rocket.center_of_mass_without_motor).to.eql(pm.environment.get('center_of_mass_without_motor'), \"rocket center_of_mass_without_motor not matching\");", - " pm.expect(apiRspn.rocket.coordinate_system_orientation).to.eql(pm.environment.get('rocket_coordinate_system_orientation'), \"rocket coordinate_system_orientation not matching\");", - " pm.expect(apiRspn.rocket.motor_position).to.eql(pm.environment.get('motor_position'), \"rocket motor_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons).to.eql(pm.environment.get('rail_buttons'), \"rocket rail_buttons not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.upper_button_position).to.eql(pm.environment.get('upper_button_position'), \"rocket rail_buttons upper_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.lower_button_position).to.eql(pm.environment.get('lower_button_position'), \"rocket rail_buttons lower_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.angular_position).to.eql(pm.environment.get('angular_position'), \"rocket rail_buttons angular_position not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket motor\", function () {", - " pm.expect(apiRspn.rocket.motor.burn_time).to.eql(pm.environment.get('burn_time'), \"rocket motor burn_time not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_mass).to.eql(pm.environment.get('dry_mass'), \"rocket motor dry_mass not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_inertia).to.eql(pm.environment.get('dry_inertia'), \"rocket motor dry_inertia not matching\");", - " pm.expect(apiRspn.rocket.motor.center_of_dry_mass_position).to.eql(pm.environment.get('center_of_dry_mass_position'), \"rocket motor center_of_dry_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_number).to.eql(pm.environment.get('grain_number'), \"rocket motor grain_number not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_density).to.eql(pm.environment.get('grain_density'), \"rocket motor grain_density not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_outer_radius).to.eql(pm.environment.get('grain_outer_radius'), \"rocket motor grain_outer_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_inner_radius).to.eql(pm.environment.get('grain_initial_inner_radius'), \"rocket motor grain_initial_inner_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_height).to.eql(pm.environment.get('grain_initial_height'), \"rocket motor grain_initial_height not matching\");", - " pm.expect(apiRspn.rocket.motor.grains_center_of_mass_position).to.eql(pm.environment.get('grains_center_of_mass_position'), \"rocket motor grains_center_of_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.thrust_source).to.eql(pm.environment.get('thrust_source'), \"rocket motor thrust_source not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_separation).to.eql(pm.environment.get('grain_separation'), \"rocket motor grain_separation not matching\");", - " pm.expect(apiRspn.rocket.motor.nozzle_radius).to.eql(pm.environment.get('nozzle_radius'), \"rocket motor nozzle_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.throat_radius).to.eql(pm.environment.get('throat_radius'), \"rocket motor throat_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.interpolation_method).to.eql(pm.environment.get('interpolation_method'), \"rocket motor interpolation_method not matching\");", - " pm.expect(apiRspn.rocket.motor.coordinate_system_orientation).to.eql(pm.environment.get('motor_coordinate_system_orientation'), \"motor coordinate_system_orientation not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket nose\", function () {", - " pm.expect(apiRspn.rocket.nose.length).to.eql(pm.environment.get('nose_length'), \"rocket nose length not matching\");", - " pm.expect(apiRspn.rocket.nose.kind).to.eql(pm.environment.get('kind'), \"rocket nose kind not matching\");", - " pm.expect(apiRspn.rocket.nose.position).to.eql(pm.environment.get('nose_position'), \"rocket nose position not matching\");", - " pm.expect(apiRspn.rocket.nose.base_radius).to.eql(pm.environment.get('base_radius'), \"rocket nose base_radius not matching\");", - " pm.expect(apiRspn.rocket.nose.rocket_radius).to.eql(pm.environment.get('rocket_radius'), \"rocket nose rocket_radius not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket fins\", function () {", - " pm.expect(apiRspn.rocket.fins.n).to.eql(pm.environment.get('n'), \"rocket fins 'n' not matching\");", - " pm.expect(apiRspn.rocket.fins.root_chord).to.eql(pm.environment.get('root_chord'), \"rocket fins root_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.tip_chord).to.eql(pm.environment.get('tip_chord'), \"rocket fins tip_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.span).to.eql(pm.environment.get('span'), \"rocket fins span not matching\");", - " pm.expect(apiRspn.rocket.fins.position).to.eql(pm.environment.get('fin_position'), \"rocket fins position not matching\");", - " pm.expect(apiRspn.rocket.fins.cant_angle).to.eql(pm.environment.get('cant_angle'), \"rocket fins cant_angle not matching\");", - " pm.expect(apiRspn.rocket.fins.radius).to.eql(pm.environment.get('fin_radius'), \"rocket fins radius not matching\");", - " pm.expect(apiRspn.rocket.fins.airfoil).to.eql(pm.environment.get('airfoil'), \"rocket fins airfoil not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket tail\", function () {", - " pm.expect(apiRspn.rocket.tail.top_radius).to.eql(pm.environment.get('top_radius'), \"rocket tail top_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.bottom_radius).to.eql(pm.environment.get('bottom_radius'), \"rocket tail bottom_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.length).to.eql(pm.environment.get('tail_length'), \"rocket tail length not matching\"); ", - " pm.expect(apiRspn.rocket.tail.position).to.eql(pm.environment.get('tail_position'), \"rocket tail position not matching\"); ", - " pm.expect(apiRspn.rocket.tail.radius).to.eql(pm.environment.get('tail_radius'), \"rocket tail radius not matching\"); ", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket parachutes\", function () {", - " pm.expect(apiRspn.rocket.parachutes.name).to.eql(pm.environment.get('parachutes_names'), \"rocket parachutes names not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.cd_s).to.eql(pm.environment.get('parachutes_cds'), \"rocket parachutes cd_s not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.sampling_rate).to.eql(pm.environment.get('parachutes_sampling_rate'), \"rocket parachutes sampling_rate not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.lag).to.eql(pm.environment.get('parachutes_lags'), \"rocket parachutes lags not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.noise).to.eql(pm.environment.get('parachutes_noises'), \"rocket parachutes noises not matching\");", - " pm.expect(apiRspn.rocket.parachutes.triggers).to.eql(pm.environment.get('parachutes_triggers'), \"rocket parachutes triggers not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_flight).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/rocketpy/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "rocketpy", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Flight simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.flight_data).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.min_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.relative_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.absolute_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.time_overshoot).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.terminate_on_apogee).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.number_of_time_steps).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.avg_function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.rail_length).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_inclination).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_heading).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.frontal_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.lateral_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_time).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_static_margin).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_angle_of_attack).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_thrust_weight_ratio).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_time).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_rocket_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_freestream_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_kinetic_energy).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_time).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_freestream_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_dynamic_pressure).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.x_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.y_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.time_of_impact).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.impact_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Drogue).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Main).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1300,\n \"latitude\": 2,\n \"longitude\": 1\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/?rocket_option=Calisto&motor_kind=Hybrid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Hybrid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save environment parameters", - "pm.environment.set('env_id', apiRspn.new_env_id) ", - "pm.environment.set('latitude', flightRequest.latitude)", - "pm.environment.set('longitude', flightRequest.longitude)", - "pm.environment.set('elevation', flightRequest.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.date) ", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"latitude\": 0,\n \"longitude\": 0,\n \"elevation\": 1400,\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"atmospheric_model_file\": \"GFS\",\n \"date\": \"2023-05-09T16:30:50.065992\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/env", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "env" - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save rocket parameters", - "pm.environment.set('rocket_id', apiRspn.new_rocket_id)", - "pm.environment.set('radius', flightRequest.radius)", - "pm.environment.set('mass', flightRequest.mass)", - "pm.environment.set('inertia', flightRequest.inertia)", - "pm.environment.set('power_off_drag', flightRequest.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.coordinate_system_orientation)", - "", - "// rocket motor", - "pm.environment.set('burn_time', flightRequest.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.motor.coordinate_system_orientation)", - "", - "// rocket nose", - "pm.environment.set('nose_length', flightRequest.nose.length)", - "pm.environment.set('kind', flightRequest.nose.kind)", - "pm.environment.set('nose_position', flightRequest.nose.position)", - "pm.environment.set('base_radius', flightRequest.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.nose.rocket_radius)", - "", - "// rocket fins", - "pm.environment.set('n', flightRequest.fins.n)", - "pm.environment.set('root_chord', flightRequest.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.fins.tip_chord)", - "pm.environment.set('span', flightRequest.fins.span)", - "pm.environment.set('fin_position', flightRequest.fins.position)", - "pm.environment.set('cant_angle', flightRequest.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.fins.radius)", - "pm.environment.set('airfoil', flightRequest.fins.airfoil)", - "", - "// rocket tail", - "pm.environment.set('top_radius', flightRequest.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.tail.length)", - "pm.environment.set('tail_position', flightRequest.tail.position)", - "pm.environment.set('tail_radius', flightRequest.tail.radius)", - "", - "// rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"motor\": {\n \"burn_time\": 6.8,\n \"center_of_dry_mass_position\": 0.512,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 10,\n \"liquid\": {\n \"density\": 10,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"center_of_mass_without_motor\": 0,\n \"radius\": 0.0632,\n \"mass\": 16.235,\n \"motor_position\": -1.255,\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"coordinate_system_orientation\": \"tail_to_nose\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/rocket?rocket_option=Calisto&motor_kind=Hybrid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "rocket" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Hybrid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Flight DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_flight_id).to.eql(pm.environment.get('flight_id'), \"flight_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Liquid", - "item": [ - { - "name": "Create Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('flight_id', apiRspn.flight_id) ", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight_id\", function () {", - " pm.expect(apiRspn.flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1400,\n \"latitude\": 0,\n \"longitude\": 0\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/?rocket_option=Calisto&motor_kind=Liquid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Liquid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "var returned_date = apiRspn.environment.date;", - "var reduced_returned_date = returned_date.substring(0, returned_date.length - 7);", - "", - "//TEST", - "bdd = \"Given a valid Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid flight \", function () {", - " pm.expect(apiRspn.inclination).to.eql(pm.environment.get('inclination'), \"flight inclination not matching\");", - " pm.expect(apiRspn.heading).to.eql(pm.environment.get('heading'), \"flight heading not matching\");", - " pm.expect(apiRspn.rail_length).to.eql(pm.environment.get('rail_length'), \"flight rail_length not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight environment\", function () {", - " pm.expect(apiRspn.environment.longitude).to.eql(pm.environment.get('longitude'), \"environment longitude not matching\"); ", - " pm.expect(apiRspn.environment.elevation).to.eql(pm.environment.get('elevation'), \"environment elevation not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_type).to.eql(pm.environment.get('atmospheric_model_type'), \"environment atmospheric_model_type not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_file).to.eql(pm.environment.get('atmospheric_model_file'), \"environment atmospheric_model_file not matching\");", - " pm.expect(reduced_returned_date).to.eql(pm.environment.get('date'), \"date not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket\", function () { ", - " pm.expect(apiRspn.rocket.radius).to.eql(pm.environment.get('radius'), \"rocket radius not matching\");", - " pm.expect(apiRspn.rocket.mass).to.eql(pm.environment.get('mass'), \"rocket mass not matching\");", - " pm.expect(apiRspn.rocket.inertia).to.eql(pm.environment.get('inertia'), \"rocket inertia not matching\");", - " pm.expect(apiRspn.rocket.power_off_drag).to.eql(pm.environment.get('power_off_drag'), \"rocket power_off_drag not matching\");", - " pm.expect(apiRspn.rocket.power_on_drag).to.eql(pm.environment.get('power_on_drag'), \"rocket power_on_drag not matching\");", - " pm.expect(apiRspn.rocket.center_of_mass_without_motor).to.eql(pm.environment.get('center_of_mass_without_motor'), \"rocket center_of_mass_without_motor not matching\");", - " pm.expect(apiRspn.rocket.coordinate_system_orientation).to.eql(pm.environment.get('rocket_coordinate_system_orientation'), \"rocket coordinate_system_orientation not matching\");", - " pm.expect(apiRspn.rocket.motor_position).to.eql(pm.environment.get('motor_position'), \"rocket motor_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons).to.eql(pm.environment.get('rail_buttons'), \"rocket rail_buttons not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.upper_button_position).to.eql(pm.environment.get('upper_button_position'), \"rocket rail_buttons upper_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.lower_button_position).to.eql(pm.environment.get('lower_button_position'), \"rocket rail_buttons lower_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.angular_position).to.eql(pm.environment.get('angular_position'), \"rocket rail_buttons angular_position not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket motor\", function () {", - " pm.expect(apiRspn.rocket.motor.burn_time).to.eql(pm.environment.get('burn_time'), \"rocket motor burn_time not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_mass).to.eql(pm.environment.get('dry_mass'), \"rocket motor dry_mass not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_inertia).to.eql(pm.environment.get('dry_inertia'), \"rocket motor dry_inertia not matching\");", - " pm.expect(apiRspn.rocket.motor.center_of_dry_mass_position).to.eql(pm.environment.get('center_of_dry_mass_position'), \"rocket motor center_of_dry_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_number).to.eql(pm.environment.get('grain_number'), \"rocket motor grain_number not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_density).to.eql(pm.environment.get('grain_density'), \"rocket motor grain_density not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_outer_radius).to.eql(pm.environment.get('grain_outer_radius'), \"rocket motor grain_outer_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_inner_radius).to.eql(pm.environment.get('grain_initial_inner_radius'), \"rocket motor grain_initial_inner_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_height).to.eql(pm.environment.get('grain_initial_height'), \"rocket motor grain_initial_height not matching\");", - " pm.expect(apiRspn.rocket.motor.grains_center_of_mass_position).to.eql(pm.environment.get('grains_center_of_mass_position'), \"rocket motor grains_center_of_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.thrust_source).to.eql(pm.environment.get('thrust_source'), \"rocket motor thrust_source not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_separation).to.eql(pm.environment.get('grain_separation'), \"rocket motor grain_separation not matching\");", - " pm.expect(apiRspn.rocket.motor.nozzle_radius).to.eql(pm.environment.get('nozzle_radius'), \"rocket motor nozzle_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.throat_radius).to.eql(pm.environment.get('throat_radius'), \"rocket motor throat_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.interpolation_method).to.eql(pm.environment.get('interpolation_method'), \"rocket motor interpolation_method not matching\");", - " pm.expect(apiRspn.rocket.motor.coordinate_system_orientation).to.eql(pm.environment.get('motor_coordinate_system_orientation'), \"motor coordinate_system_orientation not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket nose\", function () {", - " pm.expect(apiRspn.rocket.nose.length).to.eql(pm.environment.get('nose_length'), \"rocket nose length not matching\");", - " pm.expect(apiRspn.rocket.nose.kind).to.eql(pm.environment.get('kind'), \"rocket nose kind not matching\");", - " pm.expect(apiRspn.rocket.nose.position).to.eql(pm.environment.get('nose_position'), \"rocket nose position not matching\");", - " pm.expect(apiRspn.rocket.nose.base_radius).to.eql(pm.environment.get('base_radius'), \"rocket nose base_radius not matching\");", - " pm.expect(apiRspn.rocket.nose.rocket_radius).to.eql(pm.environment.get('rocket_radius'), \"rocket nose rocket_radius not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket fins\", function () {", - " pm.expect(apiRspn.rocket.fins.n).to.eql(pm.environment.get('n'), \"rocket fins 'n' not matching\");", - " pm.expect(apiRspn.rocket.fins.root_chord).to.eql(pm.environment.get('root_chord'), \"rocket fins root_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.tip_chord).to.eql(pm.environment.get('tip_chord'), \"rocket fins tip_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.span).to.eql(pm.environment.get('span'), \"rocket fins span not matching\");", - " pm.expect(apiRspn.rocket.fins.position).to.eql(pm.environment.get('fin_position'), \"rocket fins position not matching\");", - " pm.expect(apiRspn.rocket.fins.cant_angle).to.eql(pm.environment.get('cant_angle'), \"rocket fins cant_angle not matching\");", - " pm.expect(apiRspn.rocket.fins.radius).to.eql(pm.environment.get('fin_radius'), \"rocket fins radius not matching\");", - " pm.expect(apiRspn.rocket.fins.airfoil).to.eql(pm.environment.get('airfoil'), \"rocket fins airfoil not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket tail\", function () {", - " pm.expect(apiRspn.rocket.tail.top_radius).to.eql(pm.environment.get('top_radius'), \"rocket tail top_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.bottom_radius).to.eql(pm.environment.get('bottom_radius'), \"rocket tail bottom_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.length).to.eql(pm.environment.get('tail_length'), \"rocket tail length not matching\"); ", - " pm.expect(apiRspn.rocket.tail.position).to.eql(pm.environment.get('tail_position'), \"rocket tail position not matching\"); ", - " pm.expect(apiRspn.rocket.tail.radius).to.eql(pm.environment.get('tail_radius'), \"rocket tail radius not matching\"); ", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket parachutes\", function () {", - " pm.expect(apiRspn.rocket.parachutes.name).to.eql(pm.environment.get('parachutes_names'), \"rocket parachutes names not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.cd_s).to.eql(pm.environment.get('parachutes_cds'), \"rocket parachutes cd_s not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.sampling_rate).to.eql(pm.environment.get('parachutes_sampling_rate'), \"rocket parachutes sampling_rate not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.lag).to.eql(pm.environment.get('parachutes_lags'), \"rocket parachutes lags not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.noise).to.eql(pm.environment.get('parachutes_noises'), \"rocket parachutes noises not matching\");", - " pm.expect(apiRspn.rocket.parachutes.triggers).to.eql(pm.environment.get('parachutes_triggers'), \"rocket parachutes triggers not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_flight).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/rocketpy/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "rocketpy", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Flight simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.flight_data).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.min_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.relative_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.absolute_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.time_overshoot).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.terminate_on_apogee).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.number_of_time_steps).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.avg_function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.rail_length).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_inclination).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_heading).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.frontal_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.lateral_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_time).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_static_margin).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_angle_of_attack).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_thrust_weight_ratio).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_time).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_rocket_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_freestream_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_kinetic_energy).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_time).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_freestream_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_dynamic_pressure).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.x_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.y_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.time_of_impact).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.impact_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Drogue).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Main).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1300,\n \"latitude\": 2,\n \"longitude\": 1\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/?rocket_option=Calisto&motor_kind=Liquid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Liquid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save environment parameters", - "pm.environment.set('env_id', apiRspn.new_env_id) ", - "pm.environment.set('latitude', flightRequest.latitude)", - "pm.environment.set('longitude', flightRequest.longitude)", - "pm.environment.set('elevation', flightRequest.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.date) ", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"latitude\": 0,\n \"longitude\": 0,\n \"elevation\": 1400,\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"atmospheric_model_file\": \"GFS\",\n \"date\": \"2023-05-09T16:30:50.065992\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/env", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "env" - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save rocket parameters", - "pm.environment.set('rocket_id', apiRspn.new_rocket_id)", - "pm.environment.set('radius', flightRequest.radius)", - "pm.environment.set('mass', flightRequest.mass)", - "pm.environment.set('inertia', flightRequest.inertia)", - "pm.environment.set('power_off_drag', flightRequest.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.coordinate_system_orientation)", - "", - "// rocket motor", - "pm.environment.set('burn_time', flightRequest.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.motor.coordinate_system_orientation)", - "", - "// rocket nose", - "pm.environment.set('nose_length', flightRequest.nose.length)", - "pm.environment.set('kind', flightRequest.nose.kind)", - "pm.environment.set('nose_position', flightRequest.nose.position)", - "pm.environment.set('base_radius', flightRequest.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.nose.rocket_radius)", - "", - "// rocket fins", - "pm.environment.set('n', flightRequest.fins.n)", - "pm.environment.set('root_chord', flightRequest.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.fins.tip_chord)", - "pm.environment.set('span', flightRequest.fins.span)", - "pm.environment.set('fin_position', flightRequest.fins.position)", - "pm.environment.set('cant_angle', flightRequest.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.fins.radius)", - "pm.environment.set('airfoil', flightRequest.fins.airfoil)", - "", - "// rocket tail", - "pm.environment.set('top_radius', flightRequest.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.tail.length)", - "pm.environment.set('tail_position', flightRequest.tail.position)", - "pm.environment.set('tail_radius', flightRequest.tail.radius)", - "", - "// rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"motor\": {\n \"burn_time\": 6.8,\n \"center_of_dry_mass_position\": 0.512,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 10,\n \"liquid\": {\n \"density\": 10,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"center_of_mass_without_motor\": 0,\n \"radius\": 0.0632,\n \"mass\": 16.235,\n \"motor_position\": -1.255,\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"coordinate_system_orientation\": \"tail_to_nose\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/rocket?rocket_option=Calisto&motor_kind=Liquid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "rocket" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Liquid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Flight DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_flight_id).to.eql(pm.environment.get('flight_id'), \"flight_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Solid", - "item": [ - { - "name": "Create Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('flight_id', apiRspn.flight_id) ", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight_id\", function () {", - " pm.expect(apiRspn.flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1400,\n \"latitude\": 0,\n \"longitude\": 0\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/?rocket_option=Calisto&motor_kind=Solid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Solid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "var returned_date = apiRspn.environment.date;", - "var reduced_returned_date = returned_date.substring(0, returned_date.length - 7);", - "", - "//TEST", - "bdd = \"Given a valid Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid flight \", function () {", - " pm.expect(apiRspn.inclination).to.eql(pm.environment.get('inclination'), \"flight inclination not matching\");", - " pm.expect(apiRspn.heading).to.eql(pm.environment.get('heading'), \"flight heading not matching\");", - " pm.expect(apiRspn.rail_length).to.eql(pm.environment.get('rail_length'), \"flight rail_length not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight environment\", function () {", - " pm.expect(apiRspn.environment.longitude).to.eql(pm.environment.get('longitude'), \"environment longitude not matching\"); ", - " pm.expect(apiRspn.environment.elevation).to.eql(pm.environment.get('elevation'), \"environment elevation not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_type).to.eql(pm.environment.get('atmospheric_model_type'), \"environment atmospheric_model_type not matching\");", - " pm.expect(apiRspn.environment.atmospheric_model_file).to.eql(pm.environment.get('atmospheric_model_file'), \"environment atmospheric_model_file not matching\");", - " pm.expect(reduced_returned_date).to.eql(pm.environment.get('date'), \"date not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket\", function () { ", - " pm.expect(apiRspn.rocket.radius).to.eql(pm.environment.get('radius'), \"rocket radius not matching\");", - " pm.expect(apiRspn.rocket.mass).to.eql(pm.environment.get('mass'), \"rocket mass not matching\");", - " pm.expect(apiRspn.rocket.inertia).to.eql(pm.environment.get('inertia'), \"rocket inertia not matching\");", - " pm.expect(apiRspn.rocket.power_off_drag).to.eql(pm.environment.get('power_off_drag'), \"rocket power_off_drag not matching\");", - " pm.expect(apiRspn.rocket.power_on_drag).to.eql(pm.environment.get('power_on_drag'), \"rocket power_on_drag not matching\");", - " pm.expect(apiRspn.rocket.center_of_mass_without_motor).to.eql(pm.environment.get('center_of_mass_without_motor'), \"rocket center_of_mass_without_motor not matching\");", - " pm.expect(apiRspn.rocket.coordinate_system_orientation).to.eql(pm.environment.get('rocket_coordinate_system_orientation'), \"rocket coordinate_system_orientation not matching\");", - " pm.expect(apiRspn.rocket.motor_position).to.eql(pm.environment.get('motor_position'), \"rocket motor_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons).to.eql(pm.environment.get('rail_buttons'), \"rocket rail_buttons not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.upper_button_position).to.eql(pm.environment.get('upper_button_position'), \"rocket rail_buttons upper_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.lower_button_position).to.eql(pm.environment.get('lower_button_position'), \"rocket rail_buttons lower_button_position not matching\");", - " pm.expect(apiRspn.rocket.rail_buttons.angular_position).to.eql(pm.environment.get('angular_position'), \"rocket rail_buttons angular_position not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket motor\", function () {", - " pm.expect(apiRspn.rocket.motor.burn_time).to.eql(pm.environment.get('burn_time'), \"rocket motor burn_time not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_mass).to.eql(pm.environment.get('dry_mass'), \"rocket motor dry_mass not matching\");", - " pm.expect(apiRspn.rocket.motor.dry_inertia).to.eql(pm.environment.get('dry_inertia'), \"rocket motor dry_inertia not matching\");", - " pm.expect(apiRspn.rocket.motor.center_of_dry_mass_position).to.eql(pm.environment.get('center_of_dry_mass_position'), \"rocket motor center_of_dry_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_number).to.eql(pm.environment.get('grain_number'), \"rocket motor grain_number not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_density).to.eql(pm.environment.get('grain_density'), \"rocket motor grain_density not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_outer_radius).to.eql(pm.environment.get('grain_outer_radius'), \"rocket motor grain_outer_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_inner_radius).to.eql(pm.environment.get('grain_initial_inner_radius'), \"rocket motor grain_initial_inner_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_initial_height).to.eql(pm.environment.get('grain_initial_height'), \"rocket motor grain_initial_height not matching\");", - " pm.expect(apiRspn.rocket.motor.grains_center_of_mass_position).to.eql(pm.environment.get('grains_center_of_mass_position'), \"rocket motor grains_center_of_mass_position not matching\");", - " pm.expect(apiRspn.rocket.motor.thrust_source).to.eql(pm.environment.get('thrust_source'), \"rocket motor thrust_source not matching\");", - " pm.expect(apiRspn.rocket.motor.grain_separation).to.eql(pm.environment.get('grain_separation'), \"rocket motor grain_separation not matching\");", - " pm.expect(apiRspn.rocket.motor.nozzle_radius).to.eql(pm.environment.get('nozzle_radius'), \"rocket motor nozzle_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.throat_radius).to.eql(pm.environment.get('throat_radius'), \"rocket motor throat_radius not matching\");", - " pm.expect(apiRspn.rocket.motor.interpolation_method).to.eql(pm.environment.get('interpolation_method'), \"rocket motor interpolation_method not matching\");", - " pm.expect(apiRspn.rocket.motor.coordinate_system_orientation).to.eql(pm.environment.get('motor_coordinate_system_orientation'), \"motor coordinate_system_orientation not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket nose\", function () {", - " pm.expect(apiRspn.rocket.nose.length).to.eql(pm.environment.get('nose_length'), \"rocket nose length not matching\");", - " pm.expect(apiRspn.rocket.nose.kind).to.eql(pm.environment.get('kind'), \"rocket nose kind not matching\");", - " pm.expect(apiRspn.rocket.nose.position).to.eql(pm.environment.get('nose_position'), \"rocket nose position not matching\");", - " pm.expect(apiRspn.rocket.nose.base_radius).to.eql(pm.environment.get('base_radius'), \"rocket nose base_radius not matching\");", - " pm.expect(apiRspn.rocket.nose.rocket_radius).to.eql(pm.environment.get('rocket_radius'), \"rocket nose rocket_radius not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket fins\", function () {", - " pm.expect(apiRspn.rocket.fins.n).to.eql(pm.environment.get('n'), \"rocket fins 'n' not matching\");", - " pm.expect(apiRspn.rocket.fins.root_chord).to.eql(pm.environment.get('root_chord'), \"rocket fins root_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.tip_chord).to.eql(pm.environment.get('tip_chord'), \"rocket fins tip_chord not matching\");", - " pm.expect(apiRspn.rocket.fins.span).to.eql(pm.environment.get('span'), \"rocket fins span not matching\");", - " pm.expect(apiRspn.rocket.fins.position).to.eql(pm.environment.get('fin_position'), \"rocket fins position not matching\");", - " pm.expect(apiRspn.rocket.fins.cant_angle).to.eql(pm.environment.get('cant_angle'), \"rocket fins cant_angle not matching\");", - " pm.expect(apiRspn.rocket.fins.radius).to.eql(pm.environment.get('fin_radius'), \"rocket fins radius not matching\");", - " pm.expect(apiRspn.rocket.fins.airfoil).to.eql(pm.environment.get('airfoil'), \"rocket fins airfoil not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket tail\", function () {", - " pm.expect(apiRspn.rocket.tail.top_radius).to.eql(pm.environment.get('top_radius'), \"rocket tail top_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.bottom_radius).to.eql(pm.environment.get('bottom_radius'), \"rocket tail bottom_radius not matching\"); ", - " pm.expect(apiRspn.rocket.tail.length).to.eql(pm.environment.get('tail_length'), \"rocket tail length not matching\"); ", - " pm.expect(apiRspn.rocket.tail.position).to.eql(pm.environment.get('tail_position'), \"rocket tail position not matching\"); ", - " pm.expect(apiRspn.rocket.tail.radius).to.eql(pm.environment.get('tail_radius'), \"rocket tail radius not matching\"); ", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket parachutes\", function () {", - " pm.expect(apiRspn.rocket.parachutes.name).to.eql(pm.environment.get('parachutes_names'), \"rocket parachutes names not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.cd_s).to.eql(pm.environment.get('parachutes_cds'), \"rocket parachutes cd_s not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.sampling_rate).to.eql(pm.environment.get('parachutes_sampling_rate'), \"rocket parachutes sampling_rate not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.lag).to.eql(pm.environment.get('parachutes_lags'), \"rocket parachutes lags not matching\"); ", - " pm.expect(apiRspn.rocket.parachutes.noise).to.eql(pm.environment.get('parachutes_noises'), \"rocket parachutes noises not matching\");", - " pm.expect(apiRspn.rocket.parachutes.triggers).to.eql(pm.environment.get('parachutes_triggers'), \"rocket parachutes triggers not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Flight GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_flight).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/rocketpy/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "rocketpy", - "{{flight_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Flight simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.flight_data).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_position).to.exist;", - " pm.expect(apiRspn.flight_data.initial_conditions.initial_angular_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.max_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.min_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.relative_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.absolute_error_tolerance).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.time_overshoot).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.terminate_on_apogee).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.number_of_time_steps).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.numerical_integration_settings.avg_function_evaluations_per_time_step).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.rail_length).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_inclination).to.exist;", - " pm.expect(apiRspn.flight_data.launch_rail_conditions.flight_heading).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.frontal_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.surface_wind_conditions.lateral_surface_wind_speed).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_time).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_static_margin).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_angle_of_attack).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_thrust_weight_ratio).to.exist;", - " pm.expect(apiRspn.flight_data.out_of_rail_conditions.out_of_rail_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_time).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_rocket_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_freestream_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.burnout_conditions.burnout_kinetic_energy).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_time).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_altitude).to.exist;", - " pm.expect(apiRspn.flight_data.apogee_conditions.apogee_freestream_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_speed).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_mach_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_reynolds_number).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_dynamic_pressure).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_acceleration_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_during_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_gs_after_motor_burn).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_upper_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_normal_force).to.exist;", - " pm.expect(apiRspn.flight_data.maximum_values.maximum_lower_rail_button_shear_force).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.x_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.y_impact_position).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.time_of_impact).to.exist;", - " pm.expect(apiRspn.flight_data.impact_conditions.impact_velocity).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Drogue).to.exist;", - " pm.expect(apiRspn.flight_data.events_registered.events_trace.Main).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// reduce environment date for future assertion", - "flightRequest.environment.date = flightRequest.environment.date.substring(0, flightRequest.environment.date.length - 7);", - "", - "// save flight parameters", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "pm.environment.set('rail_length', flightRequest.rail_length) ", - "pm.environment.set('inclination', flightRequest.inclination)", - "pm.environment.set('heading', flightRequest.heading)", - "", - "// flight environment", - "pm.environment.set('latitude', flightRequest.environment.latitude)", - "pm.environment.set('longitude', flightRequest.environment.longitude)", - "pm.environment.set('elevation', flightRequest.environment.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.environment.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.environment.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.environment.date) ", - "", - "// flight rocket", - "pm.environment.set('radius', flightRequest.rocket.radius)", - "pm.environment.set('mass', flightRequest.rocket.mass)", - "pm.environment.set('inertia', flightRequest.rocket.inertia)", - "pm.environment.set('power_off_drag', flightRequest.rocket.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.rocket.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.rocket.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.rocket.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rocket.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rocket.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rocket.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rocket.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.rocket.coordinate_system_orientation)", - "", - "// flight rocket motor", - "pm.environment.set('burn_time', flightRequest.rocket.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.rocket.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.rocket.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.rocket.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.rocket.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.rocket.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.rocket.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.rocket.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.rocket.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.rocket.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.rocket.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.rocket.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.rocket.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.rocket.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.rocket.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.rocket.motor.coordinate_system_orientation)", - "", - "// flight rocket nose", - "pm.environment.set('nose_length', flightRequest.rocket.nose.length)", - "pm.environment.set('kind', flightRequest.rocket.nose.kind)", - "pm.environment.set('nose_position', flightRequest.rocket.nose.position)", - "pm.environment.set('base_radius', flightRequest.rocket.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.rocket.nose.rocket_radius)", - "", - "// flight rocket fins", - "pm.environment.set('n', flightRequest.rocket.fins.n)", - "pm.environment.set('root_chord', flightRequest.rocket.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.rocket.fins.tip_chord)", - "pm.environment.set('span', flightRequest.rocket.fins.span)", - "pm.environment.set('fin_position', flightRequest.rocket.fins.position)", - "pm.environment.set('cant_angle', flightRequest.rocket.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.rocket.fins.radius)", - "pm.environment.set('airfoil', flightRequest.rocket.fins.airfoil)", - "", - "// flight rocket tail", - "pm.environment.set('top_radius', flightRequest.rocket.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.rocket.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.rocket.tail.length)", - "pm.environment.set('tail_position', flightRequest.rocket.tail.position)", - "pm.environment.set('tail_radius', flightRequest.rocket.tail.radius)", - "", - "// flight rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.rocket.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.rocket.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.rocket.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.rocket.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.rocket.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.rocket.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"environment\": {\n \"atmospheric_model_file\": \"GFS\",\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"date\": \"2023-12-29T10:22:00.921396\",\n \"elevation\": 1300,\n \"latitude\": 2,\n \"longitude\": 1\n },\n \"rocket\": {\n \"center_of_mass_without_motor\": 0,\n \"coordinate_system_orientation\": \"tail_to_nose\",\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"mass\": 16.235,\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"motor_position\": -1.255,\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"radius\": 0.0632,\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n }\n },\n \"inclination\": 85,\n \"heading\": 0,\n \"rail_length\": 5.2\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/?rocket_option=Calisto&motor_kind=Solid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Solid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Environment", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save environment parameters", - "pm.environment.set('env_id', apiRspn.new_env_id) ", - "pm.environment.set('latitude', flightRequest.latitude)", - "pm.environment.set('longitude', flightRequest.longitude)", - "pm.environment.set('elevation', flightRequest.elevation) ", - "pm.environment.set('atmospheric_model_type', flightRequest.atmospheric_model_type) ", - "pm.environment.set('atmospheric_model_file', flightRequest.atmospheric_model_file) ", - "pm.environment.set('date', flightRequest.date) ", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"latitude\": 0,\n \"longitude\": 0,\n \"elevation\": 1400,\n \"atmospheric_model_type\": \"standard_atmosphere\",\n \"atmospheric_model_file\": \"GFS\",\n \"date\": \"2023-05-09T16:30:50.065992\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/env", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "env" - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Update Flight Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var flightRequest = JSON.parse(pm.request.body.raw);", - "", - "// save new flight id", - "pm.environment.set('flight_id', apiRspn.new_flight_id) ", - "", - "// save rocket parameters", - "pm.environment.set('rocket_id', apiRspn.new_rocket_id)", - "pm.environment.set('radius', flightRequest.radius)", - "pm.environment.set('mass', flightRequest.mass)", - "pm.environment.set('inertia', flightRequest.inertia)", - "pm.environment.set('power_off_drag', flightRequest.power_off_drag)", - "pm.environment.set('power_on_drag', flightRequest.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', flightRequest.center_of_mass_without_motor)", - "pm.environment.set('motor_position', flightRequest.motor_position)", - "pm.environment.set('rail_buttons', flightRequest.rail_buttons)", - "pm.environment.set('upper_button_position', flightRequest.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', flightRequest.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', flightRequest.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', flightRequest.coordinate_system_orientation)", - "", - "// rocket motor", - "pm.environment.set('burn_time', flightRequest.motor.burn_time)", - "pm.environment.set('dry_mass', flightRequest.motor.dry_mass)", - "pm.environment.set('dry_inertia', flightRequest.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', flightRequest.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', flightRequest.motor.grain_number)", - "pm.environment.set('grain_density', flightRequest.motor.grain_density)", - "pm.environment.set('grain_outer_radius', flightRequest.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', flightRequest.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', flightRequest.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', flightRequest.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', flightRequest.motor.grain_separation)", - "pm.environment.set('thrust_source', flightRequest.motor.thrust_source)", - "pm.environment.set('nozzle_radius', flightRequest.motor.nozzle_radius)", - "pm.environment.set('throat_radius', flightRequest.motor.throat_radius)", - "pm.environment.set('interpolation_method', flightRequest.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', flightRequest.motor.coordinate_system_orientation)", - "", - "// rocket nose", - "pm.environment.set('nose_length', flightRequest.nose.length)", - "pm.environment.set('kind', flightRequest.nose.kind)", - "pm.environment.set('nose_position', flightRequest.nose.position)", - "pm.environment.set('base_radius', flightRequest.nose.base_radius)", - "pm.environment.set('rocket_radius', flightRequest.nose.rocket_radius)", - "", - "// rocket fins", - "pm.environment.set('n', flightRequest.fins.n)", - "pm.environment.set('root_chord', flightRequest.fins.root_chord)", - "pm.environment.set('tip_chord', flightRequest.fins.tip_chord)", - "pm.environment.set('span', flightRequest.fins.span)", - "pm.environment.set('fin_position', flightRequest.fins.position)", - "pm.environment.set('cant_angle', flightRequest.fins.cant_angle)", - "pm.environment.set('fin_radius', flightRequest.fins.radius)", - "pm.environment.set('airfoil', flightRequest.fins.airfoil)", - "", - "// rocket tail", - "pm.environment.set('top_radius', flightRequest.tail.top_radius)", - "pm.environment.set('bottom_radius', flightRequest.tail.bottom_radius)", - "pm.environment.set('tail_length', flightRequest.tail.length)", - "pm.environment.set('tail_position', flightRequest.tail.position)", - "pm.environment.set('tail_radius', flightRequest.tail.radius)", - "", - "// rocket parachute", - "pm.environment.set('parachutes_names', flightRequest.parachutes.name)", - "pm.environment.set('parachutes_cds', flightRequest.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', flightRequest.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', flightRequest.parachutes.lag)", - "pm.environment.set('parachutes_noises', flightRequest.parachutes.noise)", - "pm.environment.set('parachutes_triggers', flightRequest.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Flight PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_flight_id\", function () {", - " pm.expect(apiRspn.new_flight_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"motor\": {\n \"burn_time\": 6.8,\n \"center_of_dry_mass_position\": 0.512,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 10,\n \"liquid\": {\n \"density\": 10,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"center_of_mass_without_motor\": 0,\n \"radius\": 0.0632,\n \"mass\": 16.235,\n \"motor_position\": -1.255,\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"coordinate_system_orientation\": \"tail_to_nose\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}/rocket?rocket_option=Calisto&motor_kind=Solid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}", - "rocket" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Solid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Flight", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Flight DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Flight successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_flight_id).to.eql(pm.environment.get('flight_id'), \"flight_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/flights/{{flight_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "flights", - "{{flight_id}}" - ] - } - }, - "response": [] - } - ] - } - ] - }, - { - "name": "Motor", - "item": [ - { - "name": "Create Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var motorRequest = JSON.parse(pm.request.body.raw);", - "", - "// save motor parameters", - "pm.environment.set('motor_id', apiRspn.motor_id) ", - "pm.environment.set('burn_time', motorRequest.burn_time)", - "pm.environment.set('dry_mass', motorRequest.dry_mass)", - "pm.environment.set('dry_inertia', motorRequest.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', motorRequest.center_of_dry_mass_position)", - "pm.environment.set('grain_number', motorRequest.grain_number)", - "pm.environment.set('grain_density', motorRequest.grain_density)", - "pm.environment.set('grain_outer_radius', motorRequest.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', motorRequest.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', motorRequest.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', motorRequest.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', motorRequest.grain_separation)", - "pm.environment.set('thrust_source', motorRequest.thrust_source)", - "pm.environment.set('nozzle_radius', motorRequest.nozzle_radius)", - "pm.environment.set('throat_radius', motorRequest.throat_radius)", - "pm.environment.set('interpolation_method', motorRequest.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', motorRequest.coordinate_system_orientation)", - "", - "//TEST", - "bdd = \"Given a valid Motor POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Motor successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid motor_id\", function () {", - " pm.expect(apiRspn.motor_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"thrust_source\": \"Cesaroni_M1670\",\n \"burn_time\": 3.9,\n \"nozzle_radius\": 0.033,\n \"dry_mass\": 1.815,\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"center_of_dry_mass_position\": 0.317,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"grain_number\": 5,\n \"grain_density\": 1815,\n \"grain_outer_radius\": 0.033,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_initial_height\": 0.12,\n \"grains_center_of_mass_position\": -0.85704,\n \"grain_separation\": 0.005,\n \"throat_radius\": 0.011,\n \"interpolation_method\": \"linear\",\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/motors/?motor_kind=Solid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "" - ], - "query": [ - { - "key": "motor_kind", - "value": "Solid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid Motor GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " }); ", - " pm.test(bdd + \" then response must contain a valid motor\", function () {", - " pm.expect(apiRspn.burn_time).to.eql(pm.environment.get('burn_time'), \"motor burn_time not matching\");", - " pm.expect(apiRspn.dry_mass).to.eql(pm.environment.get('dry_mass'), \"motor dry_mass not matching\");", - " pm.expect(apiRspn.dry_inertia).to.eql(pm.environment.get('dry_inertia'), \"motor dry_inertia not matching\");", - " pm.expect(apiRspn.center_of_dry_mass_position).to.eql(pm.environment.get('center_of_dry_mass_position'), \"motor center_of_dry_mass_position not matching\");", - " pm.expect(apiRspn.grain_number).to.eql(pm.environment.get('grain_number'), \"motor grain_number not matching\");", - " pm.expect(apiRspn.grain_density).to.eql(pm.environment.get('grain_density'), \"motor grain_density not matching\");", - " pm.expect(apiRspn.grain_outer_radius).to.eql(pm.environment.get('grain_outer_radius'), \"motor grain_outer_radius not matching\");", - " pm.expect(apiRspn.grain_initial_inner_radius).to.eql(pm.environment.get('grain_initial_inner_radius'), \"motor grain_initial_inner_radius not matching\");", - " pm.expect(apiRspn.grain_initial_height).to.eql(pm.environment.get('grain_initial_height'), \"motor grain_initial_height not matching\");", - " pm.expect(apiRspn.grains_center_of_mass_position).to.eql(pm.environment.get('grains_center_of_mass_position'), \"motor grains_center_of_mass_position not matching\");", - " pm.expect(apiRspn.thrust_source).to.eql(pm.environment.get('thrust_source'), \"motor thrust_source not matching\");", - " pm.expect(apiRspn.grain_separation).to.eql(pm.environment.get('grain_separation'), \"motor grain_separation not matching\");", - " pm.expect(apiRspn.nozzle_radius).to.eql(pm.environment.get('nozzle_radius'), \"motor nozzle_radius not matching\");", - " pm.expect(apiRspn.throat_radius).to.eql(pm.environment.get('throat_radius'), \"motor throat_radius not matching\");", - " pm.expect(apiRspn.interpolation_method).to.eql(pm.environment.get('interpolation_method'), \"motor interpolation_method not matching\");", - " pm.expect(apiRspn.coordinate_system_orientation).to.eql(pm.environment.get('motor_coordinate_system_orientation'), \"motor coordinate_system_orientation not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/motors/{{motor_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "{{motor_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Motor GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_motor).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/motors/rocketpy/{{motor_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "rocketpy", - "{{motor_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Motor simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.motor_data).to.exist;", - " pm.expect(apiRspn.motor_data.total_burning_time).to.exist;", - " pm.expect(apiRspn.motor_data.total_propellant_mass).to.exist;", - " pm.expect(apiRspn.motor_data.average_propellant_exhaust_velocity).to.exist;", - " pm.expect(apiRspn.motor_data.average_thrust).to.exist;", - " pm.expect(apiRspn.motor_data.maximum_thrust).to.exist;", - " pm.expect(apiRspn.motor_data.total_impulse).to.exist;", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/motors/{{motor_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "{{motor_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var motorRequest = JSON.parse(pm.request.body.raw);", - "", - "// save motor parameters", - "pm.environment.set('motor_id', apiRspn.new_motor_id)", - "pm.environment.set('burn_time', motorRequest.burn_time)", - "pm.environment.set('dry_mass', motorRequest.dry_mass)", - "pm.environment.set('dry_inertia', motorRequest.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', motorRequest.center_of_dry_mass_position)", - "pm.environment.set('grain_number', motorRequest.grain_number)", - "pm.environment.set('grain_density', motorRequest.grain_density)", - "pm.environment.set('grain_outer_radius', motorRequest.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', motorRequest.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', motorRequest.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', motorRequest.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', motorRequest.grain_separation)", - "pm.environment.set('thrust_source', motorRequest.thrust_source)", - "pm.environment.set('nozzle_radius', motorRequest.nozzle_radius)", - "pm.environment.set('throat_radius', motorRequest.throat_radius)", - "pm.environment.set('interpolation_method', motorRequest.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', motorRequest.coordinate_system_orientation)", - "", - "//TEST", - "bdd = \"Given a valid Motor PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Motor successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid new_motor_id\", function () {", - " pm.expect(apiRspn.new_motor_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"thrust_source\": \"Cesaroni_M1670\",\n \"burn_time\": 3.9,\n \"nozzle_radius\": 0.033,\n \"dry_mass\": 1.815,\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"center_of_dry_mass_position\": 0.317,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"grain_number\": 5,\n \"grain_density\": 1815,\n \"grain_outer_radius\": 0.033,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_initial_height\": 0.12,\n \"grains_center_of_mass_position\": -0.85704,\n \"grain_separation\": 0.005,\n \"throat_radius\": 0.011,\n \"interpolation_method\": \"linear\",\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/motors/{{motor_id}}/?motor_kind=Liquid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "{{motor_id}}", - "" - ], - "query": [ - { - "key": "motor_kind", - "value": "Liquid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Motor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Motor DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Motor successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_motor_id).to.eql(pm.environment.get('motor_id'), \"motor_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/motors/{{motor_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "motors", - "{{motor_id}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Rocket", - "item": [ - { - "name": "Create Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var rocketRequest = JSON.parse(pm.request.body.raw);", - "", - "// save rocket parameters", - "pm.environment.set('rocket_id', apiRspn.rocket_id)", - "pm.environment.set('radius', rocketRequest.radius)", - "pm.environment.set('mass', rocketRequest.mass)", - "pm.environment.set('inertia', rocketRequest.inertia)", - "pm.environment.set('power_off_drag', rocketRequest.power_off_drag)", - "pm.environment.set('power_on_drag', rocketRequest.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', rocketRequest.center_of_mass_without_motor)", - "pm.environment.set('motor_position', rocketRequest.motor_position)", - "pm.environment.set('rail_buttons', rocketRequest.rail_buttons)", - "pm.environment.set('upper_button_position', rocketRequest.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', rocketRequest.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', rocketRequest.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', rocketRequest.coordinate_system_orientation)", - "", - "// rocket motor", - "pm.environment.set('burn_time', rocketRequest.motor.burn_time)", - "pm.environment.set('dry_mass', rocketRequest.motor.dry_mass)", - "pm.environment.set('dry_inertia', rocketRequest.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', rocketRequest.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', rocketRequest.motor.grain_number)", - "pm.environment.set('grain_density', rocketRequest.motor.grain_density)", - "pm.environment.set('grain_outer_radius', rocketRequest.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', rocketRequest.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', rocketRequest.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', rocketRequest.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', rocketRequest.motor.grain_separation)", - "pm.environment.set('thrust_source', rocketRequest.motor.thrust_source)", - "pm.environment.set('nozzle_radius', rocketRequest.motor.nozzle_radius)", - "pm.environment.set('throat_radius', rocketRequest.motor.throat_radius)", - "pm.environment.set('interpolation_method', rocketRequest.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', rocketRequest.motor.coordinate_system_orientation)", - "", - "// rocket nose", - "pm.environment.set('nose_length', rocketRequest.nose.length)", - "pm.environment.set('kind', rocketRequest.nose.kind)", - "pm.environment.set('nose_position', rocketRequest.nose.position)", - "pm.environment.set('base_radius', rocketRequest.nose.base_radius)", - "pm.environment.set('rocket_radius', rocketRequest.nose.rocket_radius)", - "", - "// rocket fins", - "pm.environment.set('n', rocketRequest.fins.n)", - "pm.environment.set('root_chord', rocketRequest.fins.root_chord)", - "pm.environment.set('tip_chord', rocketRequest.fins.tip_chord)", - "pm.environment.set('span', rocketRequest.fins.span)", - "pm.environment.set('fin_position', rocketRequest.fins.position)", - "pm.environment.set('cant_angle', rocketRequest.fins.cant_angle)", - "pm.environment.set('fin_radius', rocketRequest.fins.radius)", - "pm.environment.set('airfoil', rocketRequest.fins.airfoil)", - "", - "// rocket tail", - "pm.environment.set('top_radius', rocketRequest.tail.top_radius)", - "pm.environment.set('bottom_radius', rocketRequest.tail.bottom_radius)", - "pm.environment.set('tail_length', rocketRequest.tail.length)", - "pm.environment.set('tail_position', rocketRequest.tail.position)", - "pm.environment.set('tail_radius', rocketRequest.tail.radius)", - "", - "// rocket parachute", - "pm.environment.set('parachutes_names', rocketRequest.parachutes.name)", - "pm.environment.set('parachutes_cds', rocketRequest.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', rocketRequest.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', rocketRequest.parachutes.lag)", - "pm.environment.set('parachutes_noises', rocketRequest.parachutes.noise)", - "pm.environment.set('parachutes_triggers', rocketRequest.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Rocket POST request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Rocket successfully created\");", - " });", - " pm.test(bdd + \" then response must contain a valid rocket_id\", function () {", - " pm.expect(apiRspn.rocket_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"rail_buttons\": {\n \"angular_position\": 45,\n \"lower_button_position\": 0.2,\n \"upper_button_position\": -0.5\n },\n \"motor\": {\n \"burn_time\": 3.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"center_of_mass_without_motor\": 0,\n \"radius\": 0.0632,\n \"mass\": 16.235,\n \"motor_position\": -1.255,\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"coordinate_system_orientation\": \"tail_to_nose\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/rockets/?rocket_option=Calisto&motor_kind=Solid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Solid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Read Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid Rocket GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " }); ", - " pm.test(bdd + \" then response must contain a valid rocket\", function () { ", - " pm.expect(apiRspn.radius).to.eql(pm.environment.get('radius'), \"rocket radius not matching\");", - " pm.expect(apiRspn.mass).to.eql(pm.environment.get('mass'), \"rocket mass not matching\");", - " pm.expect(apiRspn.inertia).to.eql(pm.environment.get('inertia'), \"rocket inertia not matching\");", - " pm.expect(apiRspn.power_off_drag).to.eql(pm.environment.get('power_off_drag'), \"rocket power_off_drag not matching\");", - " pm.expect(apiRspn.power_on_drag).to.eql(pm.environment.get('power_on_drag'), \"rocket power_on_drag not matching\");", - " pm.expect(apiRspn.center_of_mass_without_motor).to.eql(pm.environment.get('center_of_mass_without_motor'), \"rocket center_of_mass_without_motor not matching\");", - " pm.expect(apiRspn.coordinate_system_orientation).to.eql(pm.environment.get('rocket_coordinate_system_orientation'), \"rocket coordinate_system_orientation not matching\");", - " pm.expect(apiRspn.motor_position).to.eql(pm.environment.get('motor_position'), \"rocket motor_position not matching\");", - " pm.expect(apiRspn.rail_buttons).to.eql(pm.environment.get('rail_buttons'), \"rocket rail_buttons not matching\");", - " pm.expect(apiRspn.rail_buttons.upper_button_position).to.eql(pm.environment.get('upper_button_position'), \"rocket rail_buttons upper_button_position not matching\");", - " pm.expect(apiRspn.rail_buttons.lower_button_position).to.eql(pm.environment.get('lower_button_position'), \"rocket rail_buttons lower_button_position not matching\");", - " pm.expect(apiRspn.rail_buttons.angular_position).to.eql(pm.environment.get('angular_position'), \"rocket rail_buttons angular_position not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket motor\", function () {", - " pm.expect(apiRspn.motor.burn_time).to.eql(pm.environment.get('burn_time'), \"rocket motor burn_time not matching\");", - " pm.expect(apiRspn.motor.dry_mass).to.eql(pm.environment.get('dry_mass'), \"rocket motor dry_mass not matching\");", - " pm.expect(apiRspn.motor.dry_inertia).to.eql(pm.environment.get('dry_inertia'), \"rocket motor dry_inertia not matching\");", - " pm.expect(apiRspn.motor.center_of_dry_mass_position).to.eql(pm.environment.get('center_of_dry_mass_position'), \"rocket motor center_of_dry_mass_position not matching\");", - " pm.expect(apiRspn.motor.grain_number).to.eql(pm.environment.get('grain_number'), \"rocket motor grain_number not matching\");", - " pm.expect(apiRspn.motor.grain_density).to.eql(pm.environment.get('grain_density'), \"rocket motor grain_density not matching\");", - " pm.expect(apiRspn.motor.grain_outer_radius).to.eql(pm.environment.get('grain_outer_radius'), \"rocket motor grain_outer_radius not matching\");", - " pm.expect(apiRspn.motor.grain_initial_inner_radius).to.eql(pm.environment.get('grain_initial_inner_radius'), \"rocket motor grain_initial_inner_radius not matching\");", - " pm.expect(apiRspn.motor.grain_initial_height).to.eql(pm.environment.get('grain_initial_height'), \"rocket motor grain_initial_height not matching\");", - " pm.expect(apiRspn.motor.grains_center_of_mass_position).to.eql(pm.environment.get('grains_center_of_mass_position'), \"rocket motor grains_center_of_mass_position not matching\");", - " pm.expect(apiRspn.motor.thrust_source).to.eql(pm.environment.get('thrust_source'), \"rocket motor thrust_source not matching\");", - " pm.expect(apiRspn.motor.grain_separation).to.eql(pm.environment.get('grain_separation'), \"rocket motor grain_separation not matching\");", - " pm.expect(apiRspn.motor.nozzle_radius).to.eql(pm.environment.get('nozzle_radius'), \"rocket motor nozzle_radius not matching\");", - " pm.expect(apiRspn.motor.throat_radius).to.eql(pm.environment.get('throat_radius'), \"rocket motor throat_radius not matching\");", - " pm.expect(apiRspn.motor.interpolation_method).to.eql(pm.environment.get('interpolation_method'), \"rocket motor interpolation_method not matching\");", - " pm.expect(apiRspn.motor.coordinate_system_orientation).to.eql(pm.environment.get('motor_coordinate_system_orientation'), \"motor coordinate_system_orientation not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket nose\", function () {", - " pm.expect(apiRspn.nose.length).to.eql(pm.environment.get('nose_length'), \"rocket nose length not matching\");", - " pm.expect(apiRspn.nose.kind).to.eql(pm.environment.get('kind'), \"rocket nose kind not matching\");", - " pm.expect(apiRspn.nose.position).to.eql(pm.environment.get('nose_position'), \"rocket nose position not matching\");", - " pm.expect(apiRspn.nose.base_radius).to.eql(pm.environment.get('base_radius'), \"rocket nose base_radius not matching\");", - " pm.expect(apiRspn.nose.rocket_radius).to.eql(pm.environment.get('rocket_radius'), \"rocket nose rocket_radius not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket fins\", function () {", - " pm.expect(apiRspn.fins.n).to.eql(pm.environment.get('n'), \"rocket fins 'n' not matching\");", - " pm.expect(apiRspn.fins.root_chord).to.eql(pm.environment.get('root_chord'), \"rocket fins root_chord not matching\");", - " pm.expect(apiRspn.fins.tip_chord).to.eql(pm.environment.get('tip_chord'), \"rocket fins tip_chord not matching\");", - " pm.expect(apiRspn.fins.span).to.eql(pm.environment.get('span'), \"rocket fins span not matching\");", - " pm.expect(apiRspn.fins.position).to.eql(pm.environment.get('fin_position'), \"rocket fins position not matching\");", - " pm.expect(apiRspn.fins.cant_angle).to.eql(pm.environment.get('cant_angle'), \"rocket fins cant_angle not matching\");", - " pm.expect(apiRspn.fins.radius).to.eql(pm.environment.get('fin_radius'), \"rocket fins radius not matching\");", - " pm.expect(apiRspn.fins.airfoil).to.eql(pm.environment.get('airfoil'), \"rocket fins airfoil not matching\");", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket tail\", function () {", - " pm.expect(apiRspn.tail.top_radius).to.eql(pm.environment.get('top_radius'), \"rocket tail top_radius not matching\"); ", - " pm.expect(apiRspn.tail.bottom_radius).to.eql(pm.environment.get('bottom_radius'), \"rocket tail bottom_radius not matching\"); ", - " pm.expect(apiRspn.tail.length).to.eql(pm.environment.get('tail_length'), \"rocket tail length not matching\"); ", - " pm.expect(apiRspn.tail.position).to.eql(pm.environment.get('tail_position'), \"rocket tail position not matching\"); ", - " pm.expect(apiRspn.tail.radius).to.eql(pm.environment.get('tail_radius'), \"rocket tail radius not matching\"); ", - " });", - " pm.test(bdd + \" then response must contain a valid flight rocket parachutes\", function () {", - " pm.expect(apiRspn.parachutes.name).to.eql(pm.environment.get('parachutes_names'), \"rocket parachutes names not matching\"); ", - " pm.expect(apiRspn.parachutes.cd_s).to.eql(pm.environment.get('parachutes_cds'), \"rocket parachutes cd_s not matching\"); ", - " pm.expect(apiRspn.parachutes.sampling_rate).to.eql(pm.environment.get('parachutes_sampling_rate'), \"rocket parachutes sampling_rate not matching\"); ", - " pm.expect(apiRspn.parachutes.lag).to.eql(pm.environment.get('parachutes_lags'), \"rocket parachutes lags not matching\"); ", - " pm.expect(apiRspn.parachutes.noise).to.eql(pm.environment.get('parachutes_noises'), \"rocket parachutes noises not matching\");", - " pm.expect(apiRspn.parachutes.triggers).to.eql(pm.environment.get('parachutes_triggers'), \"rocket parachutes triggers not matching\");", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/rockets/{{rocket_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "{{rocket_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Read rocketpy Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "", - "//TEST", - "bdd = \"Given a valid rocketpy Rocket GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.jsonpickle_rocketpy_rocket).to.exist; ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/rockets/rocketpy/{{rocket_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "rocketpy", - "{{rocket_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Simulate Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid rocketpy Rocket simulate GET request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.rocket_data).to.exist;", - " pm.expect(apiRspn.rocket_data.inertia_details).to.exist;", - " pm.expect(apiRspn.rocket_data.inertia_details.rocket_mass_without_propellant).to.exist;", - " pm.expect(apiRspn.rocket_data.inertia_details.rocket_mass_with_propellant).to.exist;", - " pm.expect(apiRspn.rocket_data.inertia_details.rocket_inertia_with_motor_without_propellant).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters.rocket_maximum_radius).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters.rocket_frontal_area).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters.rocket_codm_nozzle_exit_distance).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters.rocket_codm_center_of_propellant_mass).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_geometrical_parameters.rocket_codm_loaded_center_of_mass).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities.aerodynamics_lift_coefficient_derivatives).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities.aerodynamics_center_of_pressure).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities.distance_cop_to_codm).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities.initial_static_margin).to.exist;", - " pm.expect(apiRspn.rocket_data.rocket_aerodynamics_quantities.final_static_margin).to.exist;", - " pm.expect(apiRspn.rocket_data.parachute_data).to.exist;", - " pm.expect(apiRspn.rocket_data.parachute_data.parachute_details).to.exist;", - " pm.expect(apiRspn.rocket_data.parachute_data.parachute_ejection_system_refresh_rate).to.exist;", - " pm.expect(apiRspn.rocket_data.parachute_data.parachute_lag).to.exist;", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/rockets/{{rocket_id}}/simulate", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "{{rocket_id}}", - "simulate" - ] - } - }, - "response": [] - }, - { - "name": "Update Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "var rocketRequest = JSON.parse(pm.request.body.raw);", - "", - "// save rocket parameters", - "pm.environment.set('rocket_id', apiRspn.new_rocket_id)", - "pm.environment.set('radius', rocketRequest.radius)", - "pm.environment.set('mass', rocketRequest.mass)", - "pm.environment.set('inertia', rocketRequest.inertia)", - "pm.environment.set('power_off_drag', rocketRequest.power_off_drag)", - "pm.environment.set('power_on_drag', rocketRequest.power_on_drag)", - "pm.environment.set('center_of_mass_without_motor', rocketRequest.center_of_mass_without_motor)", - "pm.environment.set('motor_position', rocketRequest.motor_position)", - "pm.environment.set('rail_buttons', rocketRequest.rail_buttons)", - "pm.environment.set('upper_button_position', rocketRequest.rail_buttons.upper_button_position)", - "pm.environment.set('lower_button_position', rocketRequest.rail_buttons.lower_button_position)", - "pm.environment.set('angular_position', rocketRequest.rail_buttons.angular_position)", - "pm.environment.set('rocket_coordinate_system_orientation', rocketRequest.coordinate_system_orientation)", - "", - "// rocket motor", - "pm.environment.set('burn_time', rocketRequest.motor.burn_time)", - "pm.environment.set('dry_mass', rocketRequest.motor.dry_mass)", - "pm.environment.set('dry_inertia', rocketRequest.motor.dry_inertia)", - "pm.environment.set('center_of_dry_mass_position', rocketRequest.motor.center_of_dry_mass_position)", - "pm.environment.set('grain_number', rocketRequest.motor.grain_number)", - "pm.environment.set('grain_density', rocketRequest.motor.grain_density)", - "pm.environment.set('grain_outer_radius', rocketRequest.motor.grain_outer_radius)", - "pm.environment.set('grain_initial_inner_radius', rocketRequest.motor.grain_initial_inner_radius)", - "pm.environment.set('grain_initial_height', rocketRequest.motor.grain_initial_height)", - "pm.environment.set('grains_center_of_mass_position', rocketRequest.motor.grains_center_of_mass_position)", - "pm.environment.set('grain_separation', rocketRequest.motor.grain_separation)", - "pm.environment.set('thrust_source', rocketRequest.motor.thrust_source)", - "pm.environment.set('nozzle_radius', rocketRequest.motor.nozzle_radius)", - "pm.environment.set('throat_radius', rocketRequest.motor.throat_radius)", - "pm.environment.set('interpolation_method', rocketRequest.motor.interpolation_method)", - "pm.environment.set('motor_coordinate_system_orientation', rocketRequest.motor.coordinate_system_orientation)", - "", - "// rocket nose", - "pm.environment.set('nose_length', rocketRequest.nose.length)", - "pm.environment.set('kind', rocketRequest.nose.kind)", - "pm.environment.set('nose_position', rocketRequest.nose.position)", - "pm.environment.set('base_radius', rocketRequest.nose.base_radius)", - "pm.environment.set('rocket_radius', rocketRequest.nose.rocket_radius)", - "", - "// rocket fins", - "pm.environment.set('n', rocketRequest.fins.n)", - "pm.environment.set('root_chord', rocketRequest.fins.root_chord)", - "pm.environment.set('tip_chord', rocketRequest.fins.tip_chord)", - "pm.environment.set('span', rocketRequest.fins.span)", - "pm.environment.set('fin_position', rocketRequest.fins.position)", - "pm.environment.set('cant_angle', rocketRequest.fins.cant_angle)", - "pm.environment.set('fin_radius', rocketRequest.fins.radius)", - "pm.environment.set('airfoil', rocketRequest.fins.airfoil)", - "", - "// rocket tail", - "pm.environment.set('top_radius', rocketRequest.tail.top_radius)", - "pm.environment.set('bottom_radius', rocketRequest.tail.bottom_radius)", - "pm.environment.set('tail_length', rocketRequest.tail.length)", - "pm.environment.set('tail_position', rocketRequest.tail.position)", - "pm.environment.set('tail_radius', rocketRequest.tail.radius)", - "", - "// rocket parachute", - "pm.environment.set('parachutes_names', rocketRequest.parachutes.name)", - "pm.environment.set('parachutes_cds', rocketRequest.parachutes.cd_s)", - "pm.environment.set('parachutes_sampling_rate', rocketRequest.parachutes.sampling_rate)", - "pm.environment.set('parachutes_lags', rocketRequest.parachutes.lag)", - "pm.environment.set('parachutes_noises', rocketRequest.parachutes.noise)", - "pm.environment.set('parachutes_triggers', rocketRequest.parachutes.triggers)", - "", - "//TEST", - "bdd = \"Given a valid Rocket PUT request is made to the API\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Rocket successfully updated\");", - " });", - " pm.test(bdd + \" then response must contain a valid rocket_id\", function () {", - " pm.expect(apiRspn.new_rocket_id).to.exist; ", - " });" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"rail_buttons\": {\n \"angular_position\": 47,\n \"lower_button_position\": 0.1,\n \"upper_button_position\": -0.2\n },\n \"motor\": {\n \"burn_time\": 5.9,\n \"center_of_dry_mass_position\": 0.317,\n \"coordinate_system_orientation\": \"nozzle_to_combustion_chamber\",\n \"dry_inertia\": [\n 0.125,\n 0.125,\n 0.002\n ],\n \"dry_mass\": 1.815,\n \"grain_density\": 1815,\n \"grain_initial_height\": 0.12,\n \"grain_initial_inner_radius\": 0.015,\n \"grain_number\": 5,\n \"grain_outer_radius\": 0.033,\n \"grain_separation\": 0.005,\n \"grains_center_of_mass_position\": -0.85704,\n \"interpolation_method\": \"linear\",\n \"nozzle_radius\": 0.033,\n \"tanks\": [\n {\n \"discretize\": 100,\n \"flux_time\": [\n 0,\n 8\n ],\n \"gas\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"gas_mass\": 0.1,\n \"gas_mass_flow_rate_in\": 0.1,\n \"gas_mass_flow_rate_out\": 0.1,\n \"geometry\": [\n [\n [\n 0,\n 5\n ],\n 1\n ],\n [\n [\n 5,\n 10\n ],\n 2\n ]\n ],\n \"initial_gas_mass\": 0.1,\n \"initial_liquid_mass\": 5,\n \"liquid\": {\n \"density\": 100,\n \"name\": \"FluidName\"\n },\n \"liquid_height\": 0.5,\n \"liquid_mass\": 5,\n \"liquid_mass_flow_rate_in\": 0.1,\n \"liquid_mass_flow_rate_out\": 0.1,\n \"name\": \"Tank\",\n \"position\": 1,\n \"tank_kind\": \"MassFlow\",\n \"ullage\": 0.1\n }\n ],\n \"throat_radius\": 0.011,\n \"thrust_source\": \"Cesaroni_M1670\"\n },\n \"nose\": {\n \"base_radius\": 0.0635,\n \"kind\": \"vonKarman\",\n \"length\": 0.55829,\n \"position\": 1.278,\n \"rocket_radius\": 0.0635\n },\n \"fins\": {\n \"airfoil\": \"\",\n \"cant_angle\": 0,\n \"n\": 4,\n \"position\": -1.04956,\n \"radius\": 0.0635,\n \"root_chord\": 0.12,\n \"span\": 0.1,\n \"tip_chord\": 0.04\n },\n \"tail\": {\n \"bottom_radius\": 0.0435,\n \"length\": 0.06,\n \"position\": -1.194656,\n \"radius\": 0.0635,\n \"top_radius\": 0.0635\n },\n \"parachutes\": {\n \"cd_s\": [\n 10,\n 1\n ],\n \"lag\": [\n 1.5,\n 1.5\n ],\n \"name\": [\n \"Main\",\n \"Drogue\"\n ],\n \"noise\": [\n [\n 0,\n 8.3,\n 0.5\n ],\n [\n 0,\n 8.3,\n 0.5\n ]\n ],\n \"sampling_rate\": [\n 105,\n 105\n ],\n \"triggers\": [\n \"lambda p, h, y: y[5] < 0 and h < 800\",\n \"lambda p, h, y: y[5] < 0\"\n ]\n },\n \"inertia\": [\n 6.321,\n 6.321,\n 0.0346\n ],\n \"center_of_mass_without_motor\": 0,\n \"radius\": 0.0632,\n \"mass\": 16.235,\n \"motor_position\": -1.255,\n \"power_off_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"power_on_drag\": [\n [\n 0.01,\n 0.333865758\n ],\n [\n 0.02,\n 0.394981721\n ],\n [\n 0.03,\n 0.407756063\n ]\n ],\n \"coordinate_system_orientation\": \"tail_to_nose\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{endpoint}}/rockets/{{rocket_id}}/?rocket_option=Calisto&motor_kind=Hybrid", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "{{rocket_id}}", - "" - ], - "query": [ - { - "key": "rocket_option", - "value": "Calisto" - }, - { - "key": "motor_kind", - "value": "Hybrid" - } - ] - }, - "description": "This returns a `token` that you can use to retrieve information later on.\n\nWe have included a test to confirm if a token is returned. We have also added test scripts to copy the token to the `token` collection variable. This makes it easy for us to reuse this token in other requests in the collection." - }, - "response": [] - }, - { - "name": "Delete Rocket", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "//Fixes the issue of breaking the collection runner whenever an http 500 is received", - "if (responseCode.code == 500) {", - " pm.test(\"Given a request is made then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " return", - "}", - "", - "var apiRspn = pm.response.json();", - "//TEST", - "bdd = \"Given a valid Rocket DELETE request is made\";", - " pm.test(bdd + \" then response must return a 200 status code\", function () {", - " pm.expect(responseCode.code).to.eql(200);", - " });", - " pm.test(bdd + \" then response must contain a valid message\", function () {", - " pm.expect(apiRspn.message).to.eql(\"Rocket successfully deleted\", \"message not matching\");", - " pm.expect(apiRspn.deleted_rocket_id).to.eql(pm.environment.get('rocket_id'), \"rocket_id not matching\"); ", - " });" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{endpoint}}/rockets/{{rocket_id}}", - "host": [ - "{{endpoint}}" - ], - "path": [ - "rockets", - "{{rocket_id}}" - ] - } - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "env_id", - "value": "" - }, - { - "key": "latitude", - "value": "" - }, - { - "key": "longitude", - "value": "" - }, - { - "key": "elevation", - "value": "" - }, - { - "key": "standard_atmosphere", - "value": "" - }, - { - "key": "atmospheric_model_type", - "value": "" - }, - { - "key": "atmospheric_model_file", - "value": "" - }, - { - "key": "date", - "value": "" - }, - { - "key": "rail_length", - "value": "" - }, - { - "key": "inclination", - "value": "" - }, - { - "key": "heading", - "value": "" - }, - { - "key": "flight_id", - "value": "" - }, - { - "key": "radius", - "value": "" - }, - { - "key": "mass", - "value": "" - }, - { - "key": "inertia", - "value": "" - }, - { - "key": "power_off_drag", - "value": "" - }, - { - "key": "power_on_drag", - "value": "" - }, - { - "key": "center_of_mass_without_motor", - "value": "" - }, - { - "key": "motor_position", - "value": "" - }, - { - "key": "rail_buttons", - "value": "" - }, - { - "key": "upper_button_position", - "value": "" - }, - { - "key": "lower_button_position", - "value": "" - }, - { - "key": "angular_position", - "value": "" - }, - { - "key": "burn_time", - "value": "" - }, - { - "key": "dry_mass", - "value": "" - }, - { - "key": "dry_inertia", - "value": "" - }, - { - "key": "center_of_dry_mass_position", - "value": "" - }, - { - "key": "grain_number", - "value": "" - }, - { - "key": "grain_density", - "value": "" - }, - { - "key": "grain_outer_radius", - "value": "" - }, - { - "key": "grain_initial_inner_radius", - "value": "" - }, - { - "key": "grain_initial_height", - "value": "" - }, - { - "key": "grains_center_of_mass_position", - "value": "" - }, - { - "key": "grain_separation", - "value": "" - }, - { - "key": "thrust_source", - "value": "" - }, - { - "key": "nozzle_radius", - "value": "" - }, - { - "key": "throat_radius", - "value": "" - }, - { - "key": "interpolation_method", - "value": "" - }, - { - "key": "coordinate_system_orientation", - "value": "" - }, - { - "key": "length", - "value": "" - }, - { - "key": "kind", - "value": "" - }, - { - "key": "position", - "value": "" - }, - { - "key": "base_radius", - "value": "" - }, - { - "key": "rocket_radius", - "value": "" - }, - { - "key": "n", - "value": "" - }, - { - "key": "root_chord", - "value": "" - }, - { - "key": "tip_chord", - "value": "" - }, - { - "key": "span", - "value": "" - }, - { - "key": "cant_angle", - "value": "" - }, - { - "key": "airfoil", - "value": "" - }, - { - "key": "top_radius", - "value": "" - }, - { - "key": "bottom_radius", - "value": "" - }, - { - "key": "parachutes_names", - "value": "" - }, - { - "key": "parachutes_cds", - "value": "" - }, - { - "key": "parachutes_sampling_rate", - "value": "" - }, - { - "key": "parachutes_lags", - "value": "" - }, - { - "key": "parachutes_noises", - "value": "" - }, - { - "key": "parachutes_triggers", - "value": "" - }, - { - "key": "nose_length", - "value": "" - }, - { - "key": "nose_position", - "value": "" - }, - { - "key": "fin_position", - "value": "" - }, - { - "key": "fin_radius", - "value": "" - }, - { - "key": "tail_length", - "value": "" - }, - { - "key": "tail_position", - "value": "" - }, - { - "key": "tail_radius", - "value": "" - }, - { - "key": "rocket_coordinate_system_orientation", - "value": "" - }, - { - "key": "motor_coordinate_system_orientation", - "value": "" - }, - { - "key": "motor_id", - "value": "" - }, - { - "key": "rocket_id", - "value": "" - } - ] -} \ No newline at end of file From 57d0a69f086cb349ce13faea4dddbaeffc7e6d01 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 7 Sep 2024 23:32:12 -0300 Subject: [PATCH 3/9] fixes motor and rocket summary route, improves separation of concerns fixes summary routes migrates from jsonpickle to dill binary pin pydantic version to ensure compatibility; upgrades python base image implements generic motor; removes rocket_options; fixes binary output addresses PR review increases response timeout; minor refactor on importing statements; fix parachute trigger evaluation context fixes pylint issues Updates pylint python version --- .github/workflows/pylint.yml | 2 +- Dockerfile | 4 +- README.md | 8 +- lib/__init__.py | 2 +- lib/__main__.py | 2 +- lib/api.py | 8 +- lib/controllers/environment.py | 34 ++- lib/controllers/flight.py | 81 ++++--- lib/controllers/motor.py | 52 +++-- lib/controllers/rocket.py | 58 +++-- lib/data/calisto/powerOffDragCurve.csv | 200 ----------------- lib/data/calisto/powerOnDragCurve.csv | 200 ----------------- lib/data/engines/CESARONI_M1400.env | 22 -- lib/data/engines/Cesaroni_7450M2505-P.eng | 16 -- lib/data/engines/Cesaroni_J360.eng | 21 -- lib/data/engines/Cesaroni_M1300.eng | 20 -- lib/data/engines/Cesaroni_M1540.eng | 16 -- lib/data/engines/Cesaroni_M1670.eng | 16 -- lib/data/engines/Cesaroni_M3100.eng | 17 -- lib/data/engines/Hypertek_J115.eng | 30 --- lib/models/aerosurfaces.py | 17 +- lib/models/motor.py | 102 ++++----- lib/models/parachute.py | 31 --- lib/models/rocket.py | 78 ++++--- lib/repositories/environment.py | 6 +- lib/repositories/flight.py | 34 +-- lib/repositories/motor.py | 15 +- lib/repositories/rocket.py | 28 +-- lib/routes/environment.py | 49 +++- lib/routes/flight.py | 59 +++-- lib/routes/motor.py | 54 ++++- lib/routes/rocket.py | 57 +++-- lib/services/environment.py | 32 ++- lib/services/flight.py | 37 ++- lib/services/motor.py | 117 ++++++++-- lib/services/rocket.py | 193 ++++++++++------ lib/settings/gunicorn.py | 2 +- lib/utils.py | 128 +++++++++++ lib/views/environment.py | 88 ++++---- lib/views/flight.py | 262 +++++++++++++--------- lib/views/motor.py | 85 +++++-- lib/views/rocket.py | 85 +++---- pyproject.toml | 17 +- requirements.txt | 3 +- 44 files changed, 1159 insertions(+), 1229 deletions(-) delete mode 100644 lib/data/calisto/powerOffDragCurve.csv delete mode 100644 lib/data/calisto/powerOnDragCurve.csv delete mode 100644 lib/data/engines/CESARONI_M1400.env delete mode 100644 lib/data/engines/Cesaroni_7450M2505-P.eng delete mode 100644 lib/data/engines/Cesaroni_J360.eng delete mode 100644 lib/data/engines/Cesaroni_M1300.eng delete mode 100644 lib/data/engines/Cesaroni_M1540.eng delete mode 100644 lib/data/engines/Cesaroni_M1670.eng delete mode 100644 lib/data/engines/Cesaroni_M3100.eng delete mode 100644 lib/data/engines/Hypertek_J115.eng delete mode 100644 lib/models/parachute.py create mode 100644 lib/utils.py diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index b135e7d..3ede0d2 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11.5"] + python-version: ["3.12.5"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/Dockerfile b/Dockerfile index 831a57b..191bffe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim-bookworm +FROM python:3.12.5-slim-bookworm EXPOSE 3000 @@ -16,4 +16,4 @@ RUN apt-get update && \ COPY ./lib /app/lib -CMD ["gunicorn", "-c", "lib/settings/gunicorn.py", "-w", "1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib.api:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "35"] +CMD ["gunicorn", "-c", "lib/settings/gunicorn.py", "-w", "1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib.api:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "60"] diff --git a/README.md b/README.md index 5a79e6c..b753926 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ - [install mongodb-atlas](https://www.mongodb.com/try/download/community) - Install dependencies `python3 -m pip install -r requirements.txt` +## Development +- black ./lib +- pylint --extension-pkg-whitelist='pydantic' ./lib/* +- flake8 --ignore E501,E402,F401,W503 ./lib + ## Starting the server - Setup MONGODB_CONNECTION_STRING: ``` @@ -62,7 +67,6 @@ $ touch .env && echo MONGODB_CONNECTION_STRING="$ConnectionString" > .env │   │   ├── environment.py │   │   ├── flight.py │   │   ├── motor.py -│   │   ├── parachute.py │   │   └── rocket.py │   │   │   └── views @@ -163,7 +167,7 @@ sequenceDiagram participant MongoDB participant Rocketpy lib - User ->> API: POST /simulate/rocketpy-model/:id + User ->> API: POST /summary/rocketpy-model/:id API -->> MongoDB: Retrieve Rocketpy Model MongoDB -->> API: Rocketpy Model API ->> Rocketpy lib: Simulate Rocketpy Model diff --git a/lib/__init__.py b/lib/__init__.py index 87832bf..171b18e 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -25,4 +25,4 @@ def parse_error(error): return f"{exc_type}: {exc_obj}" -from lib.api import app +from lib.api import app # pylint: disable=wrong-import-position,cyclic-import diff --git a/lib/__main__.py b/lib/__main__.py index 9be9014..38e9fef 100644 --- a/lib/__main__.py +++ b/lib/__main__.py @@ -2,4 +2,4 @@ from lib.api import app if __name__ == '__main__': - app.run() + app.run() # pylint: disable=no-member diff --git a/lib/api.py b/lib/api.py index 861cb3f..3716ddd 100644 --- a/lib/api.py +++ b/lib/api.py @@ -5,7 +5,6 @@ from fastapi import FastAPI, Request, status from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware -from fastapi.middleware.gzip import GZipMiddleware from fastapi.openapi.utils import get_openapi from fastapi.responses import RedirectResponse, JSONResponse @@ -14,6 +13,7 @@ from lib import logger, parse_error from lib.routes import flight, environment, motor, rocket +from lib.utils import RocketPyGZipMiddleware app = FastAPI( swagger_ui_parameters={ @@ -21,6 +21,7 @@ "syntaxHighlight.theme": "obsidian", } ) + app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -37,7 +38,7 @@ RequestsInstrumentor().instrument() # Compress responses above 1KB -app.add_middleware(GZipMiddleware, minimum_size=1000) +app.add_middleware(RocketPyGZipMiddleware, minimum_size=1000) def custom_openapi(): @@ -45,7 +46,7 @@ def custom_openapi(): return app.openapi_schema openapi_schema = get_openapi( title="RocketPy Infinity-API", - version="1.2.2 BETA", + version="2.0.0", description=( "

RocketPy Infinity-API is a RESTful Open API for RocketPy, a rocket flight simulator.

" "
" @@ -57,7 +58,6 @@ def custom_openapi(): "ReDoc" "" "

Create, manage, and simulate rocket flights, environments, rockets, and motors.

" - "

Currently, the API only supports TrapezoidalFins. We apologize for the limitation, but we are actively working to expand its capabilities soon.

" "

Please report any bugs at GitHub Issues

" ), routes=app.routes, diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index e8a58ec..fb1c91e 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -1,6 +1,5 @@ from typing import Union -import jsonpickle from fastapi import HTTPException, status from pymongo.errors import PyMongoError @@ -13,7 +12,6 @@ EnvCreated, EnvDeleted, EnvUpdated, - EnvPickle, ) @@ -91,7 +89,7 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: try: async with EnvRepository() as env_repo: await env_repo.get_env_by_id(env_id) - read_env = env_repo.env + env = env_repo.env except PyMongoError as e: logger.error( f"controllers.environment.get_env_by_id: PyMongoError {e}" @@ -110,8 +108,8 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: detail=f"Failed to read environment: {exc_str}", ) from e else: - if read_env: - return read_env + if env: + return env raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Environment not found", @@ -122,43 +120,41 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: ) @classmethod - async def get_rocketpy_env_as_jsonpickle( + async def get_rocketpy_env_binary( cls, env_id: str, - ) -> Union[EnvPickle, HTTPException]: + ) -> Union[bytes, HTTPException]: """ - Get rocketpy.Environmnet as jsonpickle string. + Get rocketpy.Environmnet dill binary. Args: env_id: str Returns: - views.EnvPickle + bytes Raises: HTTP 404 Not Found: If the env is not found in the database. """ try: - read_env = await cls.get_env_by_id(env_id) - rocketpy_env = EnvironmentService.from_env_model(read_env) + env = await cls.get_env_by_id(env_id) + env_service = EnvironmentService.from_env_model(env) except HTTPException as e: raise e from e except Exception as e: exc_str = parse_error(e) logger.error( - f"controllers.environment.get_rocketpy_env_as_jsonpickle: {exc_str}" + f"controllers.environment.get_rocketpy_env_as_binary: {exc_str}" ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read environment: {exc_str}", ) from e else: - return EnvPickle( - jsonpickle_rocketpy_env=jsonpickle.encode(rocketpy_env) - ) + return env_service.get_env_binary() finally: logger.info( - f"Call to controllers.environment.get_rocketpy_env_as_jsonpickle completed for Env {env_id}" + f"Call to controllers.environment.get_rocketpy_env_binary completed for Env {env_id}" ) async def update_env_by_id( @@ -263,9 +259,9 @@ async def simulate_env( HTTP 404 Not Found: If the env does not exist in the database. """ try: - read_env = await cls.get_env_by_id(env_id) - rocketpy_env = EnvironmentService.from_env_model(read_env) - env_summary = rocketpy_env.get_env_summary() + env = await cls.get_env_by_id(env_id) + env_service = EnvironmentService.from_env_model(env) + env_summary = env_service.get_env_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 359be3d..34c672d 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -3,18 +3,19 @@ from pymongo.errors import PyMongoError -import jsonpickle - from lib import logger, parse_error +from lib.controllers.rocket import RocketController from lib.models.environment import Env from lib.models.rocket import Rocket from lib.models.flight import Flight +from lib.views.motor import MotorView +from lib.views.rocket import RocketView from lib.views.flight import ( FlightSummary, FlightCreated, FlightUpdated, FlightDeleted, - FlightPickle, + FlightView, ) from lib.repositories.flight import FlightRepository from lib.services.flight import FlightService @@ -42,6 +43,7 @@ def __init__( self, flight: Flight, ): + self.guard(flight) self._flight = flight @property @@ -52,6 +54,10 @@ def flight(self) -> Flight: def flight(self, flight: Flight): self._flight = flight + @staticmethod + def guard(flight: Flight): + RocketController.guard(flight.rocket) + async def create_flight(self) -> Union[FlightCreated, HTTPException]: """ Create a flight in the database. @@ -85,7 +91,9 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: ) @staticmethod - async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: + async def get_flight_by_id( + flight_id: str, + ) -> Union[FlightView, HTTPException]: """ Get a flight from the database. @@ -101,7 +109,7 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: try: async with FlightRepository() as flight_repo: await flight_repo.get_flight_by_id(flight_id) - read_flight = flight_repo.flight + flight = flight_repo.flight except PyMongoError as e: logger.error( f"controllers.flight.get_flight_by_id: PyMongoError {e}" @@ -120,8 +128,20 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: detail=f"Failed to read flight: {exc_str}", ) from e else: - if read_flight: - return read_flight + if flight: + motor_view = MotorView( + **flight.rocket.motor.dict(), + selected_motor_kind=flight.rocket.motor.motor_kind, + ) + updated_rocket = flight.rocket.dict() + updated_rocket.update(motor=motor_view) + rocket_view = RocketView( + **updated_rocket, + ) + updated_flight = flight.dict() + updated_flight.update(rocket=rocket_view) + flight_view = FlightView(**updated_flight) + return flight_view raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Flight not found.", @@ -132,43 +152,41 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]: ) @classmethod - async def get_rocketpy_flight_as_jsonpickle( + async def get_rocketpy_flight_binary( cls, flight_id: str, - ) -> Union[FlightPickle, HTTPException]: + ) -> Union[bytes, HTTPException]: """ - Get rocketpy.flight as jsonpickle string. + Get rocketpy.flight as dill binary. Args: flight_id: str Returns: - views.FlightPickle + bytes Raises: HTTP 404 Not Found: If the flight is not found in the database. """ try: - read_flight = await cls.get_flight_by_id(flight_id) - rocketpy_flight = FlightService.from_flight_model(read_flight) + flight = await cls.get_flight_by_id(flight_id) + flight_service = FlightService.from_flight_model(flight) except HTTPException as e: raise e from e except Exception as e: exc_str = parse_error(e) logger.error( - f"controllers.flight.get_rocketpy_flight_as_jsonpickle: {exc_str}" + f"controllers.flight.get_rocketpy_flight_binary: {exc_str}" ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read flight: {exc_str}", ) from e else: - return FlightPickle( - jsonpickle_rocketpy_flight=jsonpickle.encode(rocketpy_flight) - ) + return flight_service.get_flight_binary() finally: logger.info( - f"Call to controllers.flight.get_rocketpy_flight_as_jsonpickle completed for Flight {flight_id}" + f"Call to controllers.flight.get_rocketpy_flight_binary completed for Flight {flight_id}" ) async def update_flight_by_id( @@ -229,11 +247,9 @@ async def update_env_by_flight_id( HTTP 404 Not Found: If the flight is not found in the database. """ try: - read_flight = await cls.get_flight_by_id(flight_id) - new_flight = read_flight.dict() - new_flight["environment"] = env - new_flight = Flight(**new_flight) - async with FlightRepository(new_flight) as flight_repo: + flight = await cls.get_flight_by_id(flight_id) + flight.environment = env + async with FlightRepository(flight) as flight_repo: await flight_repo.update_env_by_flight_id(flight_id) except PyMongoError as e: logger.error( @@ -277,16 +293,9 @@ async def update_rocket_by_flight_id( HTTP 404 Not Found: If the flight is not found in the database. """ try: - read_flight = await cls.get_flight_by_id(flight_id) - updated_rocket = rocket.dict() - updated_rocket["rocket_option"] = rocket.rocket_option.value - updated_rocket["motor"][ - "motor_kind" - ] = rocket.motor.motor_kind.value - new_flight = read_flight.dict() - new_flight["rocket"] = updated_rocket - new_flight = Flight(**new_flight) - async with FlightRepository(new_flight) as flight_repo: + flight = await cls.get_flight_by_id(flight_id) + flight.rocket = rocket + async with FlightRepository(flight) as flight_repo: await flight_repo.update_rocket_by_flight_id(flight_id) except PyMongoError as e: logger.error( @@ -371,9 +380,9 @@ async def simulate_flight( HTTP 404 Not Found: If the flight does not exist in the database. """ try: - read_flight = await cls.get_flight_by_id(flight_id=flight_id) - rocketpy_flight = FlightService.from_flight_model(read_flight) - flight_summary = rocketpy_flight.get_flight_summary() + flight = await cls.get_flight_by_id(flight_id=flight_id) + flight_service = FlightService.from_flight_model(flight) + flight_summary = flight_service.get_flight_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 23bf8ad..fc5a41a 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -1,7 +1,6 @@ from typing import Union from fastapi import HTTPException, status from pymongo.errors import PyMongoError -import jsonpickle from lib import logger, parse_error from lib.models.motor import Motor, MotorKinds @@ -12,7 +11,7 @@ MotorCreated, MotorUpdated, MotorDeleted, - MotorPickle, + MotorView, ) @@ -39,13 +38,19 @@ def motor(self) -> Motor: def motor(self, motor: Motor): self._motor = motor - def guard(self, motor: Motor): - if motor.motor_kind != MotorKinds.SOLID and motor.tanks is None: + @staticmethod + def guard(motor: Motor): + if ( + motor.motor_kind not in (MotorKinds.SOLID, MotorKinds.GENERIC) + and motor.tanks is None + ): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Tanks must be provided for liquid and hybrid motors.", ) + # TODO: extend guard to check motor kinds and tank kinds specifics + async def create_motor(self) -> Union[MotorCreated, HTTPException]: """ Create a models.Motor in the database. @@ -79,7 +84,9 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: ) @staticmethod - async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: + async def get_motor_by_id( + motor_id: str, + ) -> Union[MotorView, HTTPException]: """ Get a models.Motor from the database. @@ -95,7 +102,7 @@ async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: try: async with MotorRepository() as motor_repo: await motor_repo.get_motor_by_id(motor_id) - read_motor = motor_repo.motor + motor = motor_repo.motor except PyMongoError as e: logger.error( f"controllers.motor.get_motor_by_id: PyMongoError {e}" @@ -114,8 +121,11 @@ async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: detail=f"Failed to read motor: {exc_str}", ) from e else: - if read_motor: - return read_motor + if motor: + motor_view = MotorView( + **motor.dict(), selected_motor_kind=motor.motor_kind + ) + return motor_view raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Motor not found", @@ -126,43 +136,41 @@ async def get_motor_by_id(motor_id: str) -> Union[Motor, HTTPException]: ) @classmethod - async def get_rocketpy_motor_as_jsonpickle( + async def get_rocketpy_motor_binary( cls, motor_id: str, - ) -> Union[MotorPickle, HTTPException]: + ) -> Union[bytes, HTTPException]: """ - Get a rocketpy.Motor object as a jsonpickle string. + Get a rocketpy.Motor object as a dill binary. Args: motor_id: str Returns: - views.MotorPickle + bytes Raises: HTTP 404 Not Found: If the motor is not found in the database. """ try: - read_motor = await cls.get_motor_by_id(motor_id) - rocketpy_motor = MotorService.from_motor_model(read_motor) + motor = await cls.get_motor_by_id(motor_id) + motor_service = MotorService.from_motor_model(motor) except HTTPException as e: raise e from e except Exception as e: exc_str = parse_error(e) logger.error( - f"controllers.motor.get_rocketpy_motor_as_jsonpickle: {exc_str}" + f"controllers.motor.get_rocketpy_motor_binary: {exc_str}" ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read motor: {exc_str}", ) from e else: - return MotorPickle( - jsonpickle_rocketpy_motor=jsonpickle.encode(rocketpy_motor) - ) + return motor_service.get_motor_binary() finally: logger.info( - f"Call to controllers.motor.get_rocketpy_motor_as_jsonpickle completed for Motor {motor_id}" + f"Call to controllers.motor.get_rocketpy_motor_binary completed for Motor {motor_id}" ) async def update_motor_by_id( @@ -263,9 +271,9 @@ async def simulate_motor( HTTP 404 Not Found: If the motor does not exist in the database. """ try: - read_motor = await cls.get_motor_by_id(motor_id) - rocketpy_motor = MotorService.from_motor_model(read_motor) - motor_summary = rocketpy_motor.get_motor_summary() + motor = await cls.get_motor_by_id(motor_id) + motor_service = MotorService.from_motor_model(motor) + motor_summary = motor_service.get_motor_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index cddec3d..ef5524c 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -1,22 +1,20 @@ from typing import Union -import jsonpickle from fastapi import HTTPException, status from pymongo.errors import PyMongoError -# TODO -# from inspect import getsourcelines - from lib import logger, parse_error from lib.services.rocket import RocketService from lib.models.rocket import Rocket +from lib.controllers.motor import MotorController from lib.repositories.rocket import RocketRepository +from lib.views.motor import MotorView from lib.views.rocket import ( RocketSummary, RocketCreated, RocketUpdated, RocketDeleted, - RocketPickle, + RocketView, ) @@ -35,6 +33,7 @@ def __init__( self, rocket: Rocket, ): + self.guard(rocket) self._rocket = rocket @property @@ -45,6 +44,10 @@ def rocket(self) -> Rocket: def rocket(self, rocket: Rocket): self._rocket = rocket + @staticmethod + def guard(rocket: Rocket): + MotorController.guard(rocket.motor) + async def create_rocket(self) -> Union[RocketCreated, HTTPException]: """ Create a models.Rocket in the database. @@ -59,7 +62,7 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: 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", + detail="Failed to create rocket in the db", ) from e except HTTPException as e: raise e from e @@ -80,7 +83,7 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: @staticmethod async def get_rocket_by_id( rocket_id: str, - ) -> Union[Rocket, HTTPException]: + ) -> Union[RocketView, HTTPException]: """ Get a rocket from the database. @@ -96,7 +99,7 @@ async def get_rocket_by_id( try: async with RocketRepository() as rocket_repo: await rocket_repo.get_rocket_by_id(rocket_id) - read_rocket = rocket_repo.rocket + rocket = rocket_repo.rocket except PyMongoError as e: logger.error( f"controllers.rocket.get_rocket_by_id: PyMongoError {e}" @@ -115,8 +118,17 @@ async def get_rocket_by_id( detail=f"Failed to read rocket: {exc_str}", ) from e else: - if read_rocket: - return read_rocket + if rocket: + motor_view = MotorView( + **rocket.motor.dict(), + selected_motor_kind=rocket.motor.motor_kind, + ) + updated_rocket = rocket.dict() + updated_rocket.update(motor=motor_view) + rocket_view = RocketView( + **updated_rocket, + ) + return rocket_view raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Rocket not found", @@ -127,42 +139,40 @@ async def get_rocket_by_id( ) @classmethod - async def get_rocketpy_rocket_as_jsonpickle( + async def get_rocketpy_rocket_binary( cls, rocket_id: str - ) -> Union[RocketPickle, HTTPException]: + ) -> Union[bytes, HTTPException]: """ - Get a rocketpy.Rocket object as jsonpickle string. + Get a rocketpy.Rocket object as dill binary. Args: rocket_id: str Returns: - views.RocketPickle + bytes Raises: HTTP 404 Not Found: If the rocket is not found in the database. """ try: - read_rocket = await cls.get_rocket_by_id(rocket_id) - rocketpy_rocket = RocketService.from_rocket_model(read_rocket) + rocket = await cls.get_rocket_by_id(rocket_id) + rocket_service = RocketService.from_rocket_model(rocket) except HTTPException as e: raise e from e except Exception as e: exc_str = parse_error(e) logger.error( - f"controllers.rocket.get_rocketpy_rocket_as_jsonpickle: {exc_str}" + f"controllers.rocket.get_rocketpy_rocket_binary: {exc_str}" ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read rocket: {exc_str}", ) from e else: - return RocketPickle( - jsonpickle_rocketpy_rocket=jsonpickle.encode(rocketpy_rocket) - ) + return rocket_service.get_rocket_binary() finally: logger.info( - f"Call to controllers.rocket.get_rocketpy_rocket_as_jsonpickle completed for Rocket {rocket_id}" + f"Call to controllers.rocket.get_rocketpy_rocket_binary completed for Rocket {rocket_id}" ) async def update_rocket_by_id( @@ -264,9 +274,9 @@ async def simulate_rocket( HTTP 404 Not Found: If the rocket does not exist in the database. """ try: - read_rocket = await cls.get_rocket_by_id(rocket_id) - rocketpy_rocket = RocketService.from_rocket_model(read_rocket) - rocket_summary = rocketpy_rocket.get_rocket_summary() + rocket = await cls.get_rocket_by_id(rocket_id) + rocket_service = RocketService.from_rocket_model(rocket) + rocket_summary = rocket_service.get_rocket_summary() except HTTPException as e: raise e from e except Exception as e: diff --git a/lib/data/calisto/powerOffDragCurve.csv b/lib/data/calisto/powerOffDragCurve.csv deleted file mode 100644 index 0a90a60..0000000 --- a/lib/data/calisto/powerOffDragCurve.csv +++ /dev/null @@ -1,200 +0,0 @@ -0.01,0.333865758 -0.02,0.394981721 -0.03,0.407756063 -0.04,0.410692705 -0.05,0.410540353 -0.06,0.409240293 -0.07,0.407500874 -0.08,0.405617853 -0.09,0.403724114 -0.1,0.401881596 -0.11,0.400118527 -0.12,0.398446442 -0.13,0.396868616 -0.14,0.395383943 -0.15,0.393989178 -0.16,0.392680199 -0.17,0.391452432 -0.18,0.390301526 -0.19,0.389222867 -0.2,0.388212693 -0.21,0.3872672 -0.22,0.386382809 -0.23,0.385556609 -0.24,0.384785814 -0.25,0.3840677 -0.26,0.383461808 -0.27,0.382904488 -0.28,0.38239367 -0.29,0.381927906 -0.3,0.381505764 -0.31,0.38112576 -0.32,0.380786983 -0.33,0.380488401 -0.34,0.380229069 -0.35,0.380008442 -0.36,0.379825864 -0.37,0.379680781 -0.38,0.379572948 -0.39,0.379502053 -0.4,0.379467845 -0.41,0.379470462 -0.42,0.379509762 -0.43,0.379585951 -0.44,0.379699307 -0.45,0.379850119 -0.46,0.380038814 -0.47,0.38026598 -0.48,0.380532207 -0.49,0.380838218 -0.5,0.381184986 -0.51,0.381573462 -0.52,0.382004786 -0.53,0.382480279 -0.54,0.383061789 -0.55,0.384021453 -0.56,0.385021508 -0.57,0.386064151 -0.58,0.387151683 -0.59,0.388286902 -0.6,0.38947261 -0.61,0.390823679 -0.62,0.392227749 -0.63,0.393692485 -0.64,0.395222008 -0.65,0.396820939 -0.66,0.398494134 -0.67,0.400247115 -0.68,0.402086103 -0.69,0.404017654 -0.7,0.406049491 -0.71,0.405692833 -0.72,0.405182938 -0.73,0.404673127 -0.74,0.404163299 -0.75,0.403653676 -0.76,0.403144115 -0.77,0.402634536 -0.78,0.402125162 -0.79,0.401615748 -0.8,0.401106515 -0.81,0.406691886 -0.82,0.412277217 -0.83,0.417862728 -0.84,0.423448342 -0.85,0.429033915 -0.86,0.434619667 -0.87,0.440205521 -0.88,0.445791433 -0.89,0.451377325 -0.9,0.456963416 -0.91,0.460070768 -0.92,0.469392821 -0.93,0.486455115 -0.94,0.506596622 -0.95,0.526738129 -0.96,0.546879635 -0.97,0.567021142 -0.98,0.587162649 -0.99,0.607304156 -1,0.627445662 -1.01,0.647587169 -1.02,0.667728676 -1.03,0.687870183 -1.04,0.708011689 -1.05,0.728153196 -1.06,0.724823841 -1.07,0.721573639 -1.08,0.718399539 -1.09,0.715298768 -1.1,0.712269521 -1.11,0.709309845 -1.12,0.706418312 -1.13,0.703593424 -1.14,0.700834081 -1.15,0.698139003 -1.16,0.695198562 -1.17,0.692310349 -1.18,0.689484944 -1.19,0.686649695 -1.2,0.683689234 -1.21,0.680791411 -1.22,0.677955453 -1.23,0.675180983 -1.24,0.672467031 -1.25,0.669813183 -1.26,0.667218931 -1.27,0.664683659 -1.28,0.662207006 -1.29,0.658958242 -1.3,0.655696788 -1.31,0.652482071 -1.32,0.649313067 -1.33,0.646188665 -1.34,0.643107891 -1.35,0.640069748 -1.36,0.63707329 -1.37,0.634117922 -1.38,0.631202794 -1.39,0.628327073 -1.4,0.625490308 -1.41,0.622691519 -1.42,0.619930492 -1.43,0.617206358 -1.44,0.614518793 -1.45,0.611867277 -1.46,0.609251142 -1.47,0.606670082 -1.48,0.604123714 -1.49,0.601611411 -1.5,0.599132922 -1.51,0.596687849 -1.52,0.594275965 -1.53,0.591896725 -1.54,0.589549659 -1.55,0.587234882 -1.56,0.584951742 -1.57,0.582700041 -1.58,0.580479567 -1.59,0.578289812 -1.6,0.576130805 -1.61,0.574002135 -1.62,0.571903631 -1.63,0.569835013 -1.64,0.567796001 -1.65,0.565786432 -1.66,0.563806137 -1.67,0.561854848 -1.68,0.559932409 -1.69,0.558038559 -1.7,0.556173065 -1.71,0.554336047 -1.72,0.552526984 -1.73,0.550745911 -1.74,0.548992581 -1.75,0.547266786 -1.76,0.545568552 -1.77,0.543897563 -1.78,0.542253679 -1.79,0.54063688 -1.8,0.539046907 -1.81,0.53764434 -1.82,0.536217646 -1.83,0.534799494 -1.84,0.53338978 -1.85,0.531988364 -1.86,0.530595145 -1.87,0.529209996 -1.88,0.527832797 -1.89,0.526463462 -1.9,0.525101881 -1.91,0.523747949 -1.92,0.52240154 -1.93,0.521062597 -1.94,0.519730988 -1.95,0.51840665 -1.96,0.517089486 -1.97,0.515779376 -1.98,0.514476265 -1.99,0.513180047 -2,0.511890654 diff --git a/lib/data/calisto/powerOnDragCurve.csv b/lib/data/calisto/powerOnDragCurve.csv deleted file mode 100644 index 0a90a60..0000000 --- a/lib/data/calisto/powerOnDragCurve.csv +++ /dev/null @@ -1,200 +0,0 @@ -0.01,0.333865758 -0.02,0.394981721 -0.03,0.407756063 -0.04,0.410692705 -0.05,0.410540353 -0.06,0.409240293 -0.07,0.407500874 -0.08,0.405617853 -0.09,0.403724114 -0.1,0.401881596 -0.11,0.400118527 -0.12,0.398446442 -0.13,0.396868616 -0.14,0.395383943 -0.15,0.393989178 -0.16,0.392680199 -0.17,0.391452432 -0.18,0.390301526 -0.19,0.389222867 -0.2,0.388212693 -0.21,0.3872672 -0.22,0.386382809 -0.23,0.385556609 -0.24,0.384785814 -0.25,0.3840677 -0.26,0.383461808 -0.27,0.382904488 -0.28,0.38239367 -0.29,0.381927906 -0.3,0.381505764 -0.31,0.38112576 -0.32,0.380786983 -0.33,0.380488401 -0.34,0.380229069 -0.35,0.380008442 -0.36,0.379825864 -0.37,0.379680781 -0.38,0.379572948 -0.39,0.379502053 -0.4,0.379467845 -0.41,0.379470462 -0.42,0.379509762 -0.43,0.379585951 -0.44,0.379699307 -0.45,0.379850119 -0.46,0.380038814 -0.47,0.38026598 -0.48,0.380532207 -0.49,0.380838218 -0.5,0.381184986 -0.51,0.381573462 -0.52,0.382004786 -0.53,0.382480279 -0.54,0.383061789 -0.55,0.384021453 -0.56,0.385021508 -0.57,0.386064151 -0.58,0.387151683 -0.59,0.388286902 -0.6,0.38947261 -0.61,0.390823679 -0.62,0.392227749 -0.63,0.393692485 -0.64,0.395222008 -0.65,0.396820939 -0.66,0.398494134 -0.67,0.400247115 -0.68,0.402086103 -0.69,0.404017654 -0.7,0.406049491 -0.71,0.405692833 -0.72,0.405182938 -0.73,0.404673127 -0.74,0.404163299 -0.75,0.403653676 -0.76,0.403144115 -0.77,0.402634536 -0.78,0.402125162 -0.79,0.401615748 -0.8,0.401106515 -0.81,0.406691886 -0.82,0.412277217 -0.83,0.417862728 -0.84,0.423448342 -0.85,0.429033915 -0.86,0.434619667 -0.87,0.440205521 -0.88,0.445791433 -0.89,0.451377325 -0.9,0.456963416 -0.91,0.460070768 -0.92,0.469392821 -0.93,0.486455115 -0.94,0.506596622 -0.95,0.526738129 -0.96,0.546879635 -0.97,0.567021142 -0.98,0.587162649 -0.99,0.607304156 -1,0.627445662 -1.01,0.647587169 -1.02,0.667728676 -1.03,0.687870183 -1.04,0.708011689 -1.05,0.728153196 -1.06,0.724823841 -1.07,0.721573639 -1.08,0.718399539 -1.09,0.715298768 -1.1,0.712269521 -1.11,0.709309845 -1.12,0.706418312 -1.13,0.703593424 -1.14,0.700834081 -1.15,0.698139003 -1.16,0.695198562 -1.17,0.692310349 -1.18,0.689484944 -1.19,0.686649695 -1.2,0.683689234 -1.21,0.680791411 -1.22,0.677955453 -1.23,0.675180983 -1.24,0.672467031 -1.25,0.669813183 -1.26,0.667218931 -1.27,0.664683659 -1.28,0.662207006 -1.29,0.658958242 -1.3,0.655696788 -1.31,0.652482071 -1.32,0.649313067 -1.33,0.646188665 -1.34,0.643107891 -1.35,0.640069748 -1.36,0.63707329 -1.37,0.634117922 -1.38,0.631202794 -1.39,0.628327073 -1.4,0.625490308 -1.41,0.622691519 -1.42,0.619930492 -1.43,0.617206358 -1.44,0.614518793 -1.45,0.611867277 -1.46,0.609251142 -1.47,0.606670082 -1.48,0.604123714 -1.49,0.601611411 -1.5,0.599132922 -1.51,0.596687849 -1.52,0.594275965 -1.53,0.591896725 -1.54,0.589549659 -1.55,0.587234882 -1.56,0.584951742 -1.57,0.582700041 -1.58,0.580479567 -1.59,0.578289812 -1.6,0.576130805 -1.61,0.574002135 -1.62,0.571903631 -1.63,0.569835013 -1.64,0.567796001 -1.65,0.565786432 -1.66,0.563806137 -1.67,0.561854848 -1.68,0.559932409 -1.69,0.558038559 -1.7,0.556173065 -1.71,0.554336047 -1.72,0.552526984 -1.73,0.550745911 -1.74,0.548992581 -1.75,0.547266786 -1.76,0.545568552 -1.77,0.543897563 -1.78,0.542253679 -1.79,0.54063688 -1.8,0.539046907 -1.81,0.53764434 -1.82,0.536217646 -1.83,0.534799494 -1.84,0.53338978 -1.85,0.531988364 -1.86,0.530595145 -1.87,0.529209996 -1.88,0.527832797 -1.89,0.526463462 -1.9,0.525101881 -1.91,0.523747949 -1.92,0.52240154 -1.93,0.521062597 -1.94,0.519730988 -1.95,0.51840665 -1.96,0.517089486 -1.97,0.515779376 -1.98,0.514476265 -1.99,0.513180047 -2,0.511890654 diff --git a/lib/data/engines/CESARONI_M1400.env b/lib/data/engines/CESARONI_M1400.env deleted file mode 100644 index b38f5ca..0000000 --- a/lib/data/engines/CESARONI_M1400.env +++ /dev/null @@ -1,22 +0,0 @@ -; -; Cesaroni Pro75 6251M1400 -; 'Classic Propellant' -; -; RockSim file by Kathy Miller -; wRasp Adaptation by Len Lekx -; -M1400 75 757 0 2.99 5.30 CTI -0.10 1993.60 -0.50 1891.25 -1.10 1780.00 -1.50 1691.00 -2.00 1602.00 -2.30 1557.50 -2.50 1513.00 -3.00 1335.00 -3.50 1223.75 -3.70 1112.00 -3.90 667.50 -4.00 534.00 -4.40 222.50 -4.47 0.00 diff --git a/lib/data/engines/Cesaroni_7450M2505-P.eng b/lib/data/engines/Cesaroni_7450M2505-P.eng deleted file mode 100644 index 1445406..0000000 --- a/lib/data/engines/Cesaroni_7450M2505-P.eng +++ /dev/null @@ -1,16 +0,0 @@ -; Motor Data From RocketReviews.com -; http://www.rocketreviews.com/cti-m2505-classic.html -7450M2505-P 98.0000 548.0000 Plugged 3423.0000 6258.0000 CTI -0.1200 2600.0000 -0.2100 2482.0000 -0.6000 2715.0000 -0.9000 2876.0000 -1.2000 2938.0000 -1.5000 2889.0000 -1.8000 2785.0000 -2.1000 2573.0000 -2.4000 2349.0000 -2.7000 2182.0000 -2.9900 85.0000 -3.0000 0.0000 -; diff --git a/lib/data/engines/Cesaroni_J360.eng b/lib/data/engines/Cesaroni_J360.eng deleted file mode 100644 index 3872713..0000000 --- a/lib/data/engines/Cesaroni_J360.eng +++ /dev/null @@ -1,21 +0,0 @@ -; CTI Pro54-3G 1016 J3360 Skidmark 15A -J360SM 54 321 6-8-10-12-15 0.606 1.104 CTI - 0.017 560.653 - 0.058 287.423 - 0.167 328.23 - 0.358 363.715 - 0.538 374.36 - 0.85 397.425 - 1.163 400.973 - 1.458 400.973 - 1.733 395.651 - 1.983 381.457 - 2.271 356.618 - 2.496 340.65 - 2.592 351.295 - 2.646 289.198 - 2.679 172.099 - 2.725 86.937 - 2.804 19.516 - 2.904 5.323 - 2.975 0.0 diff --git a/lib/data/engines/Cesaroni_M1300.eng b/lib/data/engines/Cesaroni_M1300.eng deleted file mode 100644 index 40cbbdf..0000000 --- a/lib/data/engines/Cesaroni_M1300.eng +++ /dev/null @@ -1,20 +0,0 @@ -; Pro75-5G 6438M1300-IM/DT P -M1300-IM 75 757 P 3.595 5.657 CTI - 0.0000 0.0000 - 0.0090 394.105 - 0.057 934.778 - 0.086 2146.406 - 0.154 2615.423 - 0.314 2827.132 - 0.671 2758.734 - 0.97 2752.22 - 1.082 1172.543 - 1.187 1120.43 - 2.14 1172.543 - 2.7 1139.973 - 3.884 915.235 - 4.372 771.924 - 4.6 400.619 - 4.697 335.478 - 4.9 120.511 - 4.901 0.0 diff --git a/lib/data/engines/Cesaroni_M1540.eng b/lib/data/engines/Cesaroni_M1540.eng deleted file mode 100644 index 9f89169..0000000 --- a/lib/data/engines/Cesaroni_M1540.eng +++ /dev/null @@ -1,16 +0,0 @@ -M1540-IM 75 757 0 3.778 5.906 CTI -0.02 800 -0.04 1250 -0.06 1800 -0.08 2400 -0.15 2060 -0.2 2000 -0.35 2100 -0.55 1940 -0.7 1900 -1.7 1830 -2.5 1720 -3.38 1550 -3.83 680 -4 530 -4.5 0 diff --git a/lib/data/engines/Cesaroni_M1670.eng b/lib/data/engines/Cesaroni_M1670.eng deleted file mode 100644 index bf6a120..0000000 --- a/lib/data/engines/Cesaroni_M1670.eng +++ /dev/null @@ -1,16 +0,0 @@ -M1670-BS 75 757 0 3.101 5.231 CTI -0.055 100 -0.092 1500 -0.1 2000 -0.15 2200 -0.2 1800 -0.5 1950 -1 2034 -1.5 2000 -2 1900 -2.5 1760 -2.9 1700 -3 1650 -3.3 530 -3.4 350 -3.9 0 diff --git a/lib/data/engines/Cesaroni_M3100.eng b/lib/data/engines/Cesaroni_M3100.eng deleted file mode 100644 index 492a59d..0000000 --- a/lib/data/engines/Cesaroni_M3100.eng +++ /dev/null @@ -1,17 +0,0 @@ -; Pro75-5G 6118M3100-WT P -M3100-WT 75 757 P 2.95 5.018 CTI - 0.02 3118.031 - 0.057 2976.886 - 0.148 3186.465 - 0.496 3391.768 - 0.817 3665.504 - 0.936 3532.913 - 1.173 3357.551 - 1.501 3199.297 - 1.717 3139.417 - 1.78 2412.304 - 1.812 2130.013 - 1.832 2031.639 - 1.937 346.448 - 1.985 81.266 - 2.0 0.0 diff --git a/lib/data/engines/Hypertek_J115.eng b/lib/data/engines/Hypertek_J115.eng deleted file mode 100644 index 9ce0925..0000000 --- a/lib/data/engines/Hypertek_J115.eng +++ /dev/null @@ -1,30 +0,0 @@ -; HyperTek J115 (440CC076J) -; converted from TMT test stand data 2001 (www.tripoli.org) -; provided by ThrustCurve.org (www.thrustcurve.org) -J115 54 614 0 0.411264 1.28218 HT - 0.129 218.303 - 0.391 230.563 - 0.653 216.171 - 0.916 165.676 - 1.178 158.834 - 1.441 161.888 - 1.703 157.955 - 1.966 152.977 - 2.228 148.337 - 2.491 141.919 - 2.753 136.970 - 3.016 129.152 - 3.278 121.815 - 3.541 111.971 - 3.803 79.163 - 4.066 53.433 - 4.328 42.975 - 4.591 38.391 - 4.853 33.418 - 5.116 28.709 - 5.378 23.886 - 5.641 19.658 - 5.903 15.894 - 6.166 11.955 - 6.428 9.151 - 6.691 0.000 diff --git a/lib/models/aerosurfaces.py b/lib/models/aerosurfaces.py index 1a88fbc..14f0406 100644 --- a/lib/models/aerosurfaces.py +++ b/lib/models/aerosurfaces.py @@ -1,14 +1,16 @@ -from typing import Optional +from enum import Enum from pydantic import BaseModel class RailButtons(BaseModel): + name: str upper_button_position: float lower_button_position: float angular_position: float class NoseCone(BaseModel): + name: str length: float kind: str position: float @@ -16,7 +18,14 @@ class NoseCone(BaseModel): rocket_radius: float +class FinsKinds(str, Enum): + TRAPEZOIDAL: str = "TRAPEZOIDAL" + ELLIPTICAL: str = "ELLIPTICAL" + + class Fins(BaseModel): + fins_kind: FinsKinds + name: str n: int root_chord: float tip_chord: float @@ -27,11 +36,13 @@ class Fins(BaseModel): airfoil: str -class TrapezoidalFins(Fins): - pass +# TODO: implement airbrakes +class AirBrakes(BaseModel): + name: str class Tail(BaseModel): + name: str top_radius: float bottom_radius: float length: float diff --git a/lib/models/motor.py b/lib/models/motor.py index c78b101..3d58f29 100644 --- a/lib/models/motor.py +++ b/lib/models/motor.py @@ -1,26 +1,15 @@ from enum import Enum -from typing import Optional, Tuple, List, Union -from rocketpy import ( - LevelBasedTank, - MassBasedTank, - MassFlowRateBasedTank, - UllageBasedTank, - TankGeometry, -) +from typing import Optional, Tuple, List from pydantic import BaseModel, PrivateAttr class MotorKinds(str, Enum): HYBRID: str = "HYBRID" SOLID: str = "SOLID" + GENERIC: str = "GENERIC" LIQUID: str = "LIQUID" -class MotorEngines(str, Enum): - CESARONI_M1670: str = "CESARONI_M1670" - CUSTOM: str = "CUSTOM" - - class TankKinds(str, Enum): LEVEL: str = "LEVEL" MASS: str = "MASS" @@ -28,6 +17,11 @@ class TankKinds(str, Enum): ULLAGE: str = "ULLAGE" +class CoordinateSystemOrientation(str, Enum): + NOZZLE_TO_COMBUSTION_CHAMBER: str = "NOZZLE_TO_COMBUSTION_CHAMBER" + COMBUSTION_CHAMBER_TO_NOZZLE: str = "COMBUSTION_CHAMBER_TO_NOZZLE" + + class TankFluids(BaseModel): name: str density: float @@ -39,83 +33,57 @@ class MotorTank(BaseModel): ((0.0, 5.0), 1.0), ((5.0, 10.0), 2.0), ] - tank_kind: TankKinds = TankKinds.MASS_FLOW gas: TankFluids = TankFluids(name="GAS", density=100) liquid: TankFluids = TankFluids(name="LIQUID", density=1000) - name: str = "Tank" flux_time: Tuple[float, float] = (0.0, 3.9) position: float = 1.0 discretize: int = 100 - # Optional parameters + # Level based tank parameters liquid_height: Optional[float] = 0.5 + + # Mass based tank parameters liquid_mass: Optional[float] = 5.0 gas_mass: Optional[float] = 0.1 + + # Mass flow based tank parameters gas_mass_flow_rate_in: Optional[float] = 0.0 gas_mass_flow_rate_out: Optional[float] = 0.1 liquid_mass_flow_rate_in: Optional[float] = 0.0 liquid_mass_flow_rate_out: Optional[float] = 1 initial_liquid_mass: Optional[float] = 5.0 initial_gas_mass: Optional[float] = 0.4 + + # Ullage based tank parameters ullage: Optional[float] = 0.1 - _tank: Union[ - LevelBasedTank, MassBasedTank, MassFlowRateBasedTank, UllageBasedTank - ] = PrivateAttr() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - tank_core = { - "name": self.name, - "geometry": TankGeometry(geometry_dict=dict(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) - self._tank = tank + # Optional parameters + name: Optional[str] = "Tank" - @property - def tank(self): - return self._tank + # Computed parameters + tank_kind: TankKinds = TankKinds.MASS_FLOW class Motor(BaseModel): # Required parameters - thrust_source: MotorEngines = MotorEngines.CESARONI_M1670 + thrust_source: List[List[float]] = [[0.0, 0.0], [1.0, 1.0]] 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 - _motor_kind: MotorKinds = PrivateAttr(default=MotorKinds.SOLID) - # Optional parameters + # Generic motor parameters + chamber_radius: Optional[float] = 0.033 + chamber_height: Optional[float] = 0.1 + chamber_position: Optional[float] = 0.0 + propellant_initial_mass: Optional[float] = 1.0 + nozzle_position: Optional[float] = 0.0 + + # Liquid motor parameters tanks: Optional[List[MotorTank]] = [MotorTank()] + + # Solid motor parameters grain_number: Optional[int] = 5 grain_density: Optional[float] = 1815 grain_outer_radius: Optional[float] = 0.033 @@ -123,11 +91,19 @@ class Motor(BaseModel): grain_initial_height: Optional[float] = 0.12 grains_center_of_mass_position: Optional[float] = -0.85704 grain_separation: Optional[float] = 0.005 + + # Hybrid motor parameters throat_radius: Optional[float] = 0.011 + + # Optional parameters interpolation_method: Optional[str] = "linear" - coordinate_system_orientation: Optional[str] = ( - "nozzle_to_combustion_chamber" + coordinate_system_orientation: Optional[CoordinateSystemOrientation] = ( + CoordinateSystemOrientation.NOZZLE_TO_COMBUSTION_CHAMBER ) + reshape_thrust_curve: Optional[bool] = False + + # Computed parameters + _motor_kind: MotorKinds = PrivateAttr(default=MotorKinds.SOLID) @property def motor_kind(self) -> MotorKinds: diff --git a/lib/models/parachute.py b/lib/models/parachute.py deleted file mode 100644 index 42cc13d..0000000 --- a/lib/models/parachute.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import List, Tuple -from pydantic import BaseModel - - -class Parachute(BaseModel): - name: List[str] = ["Main", "Drogue"] - cd_s: List[float] = [10, 1] - 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", - ] - - def __getitem__(self, idx): - if isinstance(idx, slice): - return [self[i] for i in range(*idx.indices(len(self)))] - return Parachute( - name=[self.name[idx]], - cd_s=[self.cd_s[idx]], - triggers=[self.triggers[idx]], - sampling_rate=[self.sampling_rate[idx]], - lag=[self.lag[idx]], - noise=[self.noise[idx]], - ) - - def __len__(self): - if self.name is not None: - return len(self.name) - return 0 diff --git a/lib/models/rocket.py b/lib/models/rocket.py index 60d2173..401826e 100644 --- a/lib/models/rocket.py +++ b/lib/models/rocket.py @@ -1,14 +1,28 @@ from enum import Enum from typing import Optional, Tuple, List -from pydantic import BaseModel, PrivateAttr +from pydantic import BaseModel from lib.models.motor import Motor -from lib.models.aerosurfaces import Fins, NoseCone, Tail, RailButtons -from lib.models.parachute import Parachute +from lib.models.aerosurfaces import ( + Fins, + NoseCone, + Tail, + RailButtons, + FinsKinds, +) -class RocketOptions(str, Enum): - CALISTO: str = "CALISTO" - CUSTOM: str = "CUSTOM" +class CoordinateSystemOrientation(str, Enum): + TAIL_TO_NOSE: str = "TAIL_TO_NOSE" + NOSE_TO_TAIL: str = "NOSE_TO_TAIL" + + +class Parachute(BaseModel): + name: str = "Main" + cd_s: float = 10 + sampling_rate: int = 105 + lag: float = 1.5 + trigger: str = "lambda p, h, y: y[5] < 0 and h < 800" + noise: Tuple[float, float, float] = (0, 8.3, 0.5) class Rocket(BaseModel): @@ -17,22 +31,38 @@ class Rocket(BaseModel): radius: float = 0.0632 mass: float = 16.235 motor_position: float = -1.255 - parachutes: Parachute = Parachute() center_of_mass_without_motor: int = 0 inertia: Tuple[float, float, float] = (6.321, 6.321, 0.0346) - rail_buttons: RailButtons = RailButtons( + power_off_drag: List[Tuple[float, float]] = [ + (0.0, 0.0), + (0.1, 0.1), + (0.2, 0.2), + ] + power_on_drag: List[Tuple[float, float]] = [ + (0.0, 0.0), + (0.1, 0.1), + (0.2, 0.2), + ] + + # Optional parameters + parachutes: Optional[List[Parachute]] = [Parachute()] + rail_buttons: Optional[RailButtons] = RailButtons( + name="RailButtons", upper_button_position=-0.5, lower_button_position=0.2, angular_position=45, ) - nose: NoseCone = NoseCone( + nose: Optional[NoseCone] = NoseCone( + name="Nose", length=0.55829, kind="vonKarman", position=1.278, base_radius=0.0635, rocket_radius=0.0635, ) - fins: Fins = Fins( + fins: Optional[Fins] = Fins( + fins_kind=FinsKinds.TRAPEZOIDAL, + name="Fins", n=4, root_chord=0.12, tip_chord=0.04, @@ -42,32 +72,14 @@ class Rocket(BaseModel): radius=0.0635, airfoil="", ) - tail: Tail = Tail( + tail: Optional[Tail] = Tail( + name="Tail", top_radius=0.0635, bottom_radius=0.0435, length=0.06, position=-1.194656, radius=0.0635, ) - _rocket_option: RocketOptions = PrivateAttr(default=RocketOptions.CALISTO) - - # Optional parameters - # TODO: implement field validation so a list of possible tailToNose values is provided in the api docs - power_off_drag: Optional[List[Tuple[float, float]]] = [ - (0.01, 0.333865758), - (0.02, 0.394981721), - (0.03, 0.407756063), - ] - power_on_drag: Optional[List[Tuple[float, float]]] = [ - (0.01, 0.333865758), - (0.02, 0.394981721), - (0.03, 0.407756063), - ] - coordinate_system_orientation: Optional[str] = "tail_to_nose" - - @property - def rocket_option(self) -> RocketOptions: - return self._rocket_option - - def set_rocket_option(self, rocket_option: RocketOptions): - self._rocket_option = rocket_option + coordinate_system_orientation: Optional[CoordinateSystemOrientation] = ( + CoordinateSystemOrientation.TAIL_TO_NOSE + ) diff --git a/lib/repositories/environment.py b/lib/repositories/environment.py index 5cc3263..56dac18 100644 --- a/lib/repositories/environment.py +++ b/lib/repositories/environment.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Self from bson import ObjectId from pymongo.errors import PyMongoError from lib.models.environment import Env @@ -54,7 +54,7 @@ async def find_env(self, env_id: str): async def delete_env(self, env_id: str): collection = self.get_collection() - await collection.delete_one({"env_id": env_id}) + await collection.delete_one({"_id": ObjectId(env_id)}) return self async def create_env(self): @@ -78,7 +78,7 @@ async def create_env(self): f"Call to repositories.environment.create_env completed for Env {self.env_id}" ) - async def get_env_by_id(self, env_id: str) -> Union[Env, None]: + async def get_env_by_id(self, env_id: str) -> Self: """ Gets a models.Env from the database diff --git a/lib/repositories/flight.py b/lib/repositories/flight.py index edc4cd9..0c5f1a9 100644 --- a/lib/repositories/flight.py +++ b/lib/repositories/flight.py @@ -1,9 +1,8 @@ -from typing import Union +from typing import Self from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger from lib.models.flight import Flight -from lib.models.rocket import RocketOptions from lib.models.motor import MotorKinds from lib.repositories.repo import Repository, RepositoryNotInitializedException @@ -70,7 +69,7 @@ async def find_flight(self, flight_id: str): async def delete_flight(self, flight_id: str): collection = self.get_collection() - await collection.delete_one({"flight_id": flight_id}) + await collection.delete_one({"_id": ObjectId(flight_id)}) return self async def create_flight(self): @@ -82,9 +81,6 @@ async def create_flight(self): """ try: flight_to_dict = self.flight.dict() - flight_to_dict["rocket"][ - "rocket_option" - ] = self.flight.rocket.rocket_option.value flight_to_dict["rocket"]["motor"][ "motor_kind" ] = self.flight.rocket.motor.motor_kind.value @@ -100,7 +96,7 @@ async def create_flight(self): f"Call to repositories.flight.create_flight completed for Flight {self.flight_id}" ) - async def get_flight_by_id(self, flight_id: str) -> Union[Flight, None]: + async def get_flight_by_id(self, flight_id: str) -> Self: """ Gets a models.Flight from the database @@ -109,16 +105,12 @@ async def get_flight_by_id(self, flight_id: str) -> Union[Flight, None]: """ try: read_flight = await self.find_flight(flight_id) - parsed_flight = ( - Flight.parse_obj(read_flight) if read_flight else None - ) - parsed_flight.rocket.motor.set_motor_kind( - MotorKinds(read_flight["rocket"]["motor"]["motor_kind"]) - ) - parsed_flight.rocket.set_rocket_option( - RocketOptions(read_flight["rocket"]["rocket_option"]) - ) - self.flight = parsed_flight + if read_flight: + parsed_flight = Flight.parse_obj(read_flight) + parsed_flight.rocket.motor.set_motor_kind( + MotorKinds(read_flight["rocket"]["motor"]["motor_kind"]) + ) + self.flight = parsed_flight except PyMongoError as e: raise e from e except RepositoryNotInitializedException as e: @@ -159,9 +151,6 @@ async def update_flight_by_id(self, flight_id: str): """ try: flight_to_dict = self.flight.dict() - flight_to_dict["rocket"][ - "rocket_option" - ] = self.flight.rocket.rocket_option.value flight_to_dict["rocket"]["motor"][ "motor_kind" ] = self.flight.rocket.motor.motor_kind.value @@ -185,7 +174,7 @@ async def update_env_by_flight_id(self, flight_id: str): self """ try: - env_to_dict = self.flight.env.dict() + env_to_dict = self.flight.environment.dict() await self.update_env(env_to_dict, flight_id) except PyMongoError as e: raise e from e @@ -207,9 +196,6 @@ async def update_rocket_by_flight_id(self, flight_id: str): """ try: rocket_to_dict = self.flight.rocket.dict() - rocket_to_dict["rocket_option"] = ( - self.flight.rocket.rocket_option.value - ) rocket_to_dict["motor"][ "motor_kind" ] = self.flight.rocket.motor.motor_kind.value diff --git a/lib/repositories/motor.py b/lib/repositories/motor.py index c414654..f63be78 100644 --- a/lib/repositories/motor.py +++ b/lib/repositories/motor.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Self from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger @@ -54,7 +54,7 @@ async def find_motor(self, motor_id: str): async def delete_motor(self, motor_id: str): collection = self.get_collection() - await collection.delete_one({"motor_id": motor_id}) + await collection.delete_one({"_id": ObjectId(motor_id)}) return self async def create_motor(self): @@ -79,7 +79,7 @@ async def create_motor(self): f"Call to repositories.motor.create_motor completed for Motor {self.motor_id}" ) - async def get_motor_by_id(self, motor_id: str) -> Union[motor, None]: + async def get_motor_by_id(self, motor_id: str) -> Self: """ Gets a models.Motor from the database @@ -88,9 +88,12 @@ async def get_motor_by_id(self, motor_id: str) -> Union[motor, None]: """ try: read_motor = await self.find_motor(motor_id) - parsed_motor = Motor.parse_obj(read_motor) if read_motor else None - parsed_motor.set_motor_kind(MotorKinds(read_motor["motor_kind"])) - self.motor = parsed_motor + if read_motor: + parsed_motor = Motor.parse_obj(read_motor) + parsed_motor.set_motor_kind( + MotorKinds(read_motor["motor_kind"]) + ) + self.motor = parsed_motor except PyMongoError as e: raise e from e except RepositoryNotInitializedException as e: diff --git a/lib/repositories/rocket.py b/lib/repositories/rocket.py index cf0954b..951f9f1 100644 --- a/lib/repositories/rocket.py +++ b/lib/repositories/rocket.py @@ -1,8 +1,8 @@ -from typing import Union +from typing import Self from bson import ObjectId from pymongo.errors import PyMongoError from lib import logger -from lib.models.rocket import Rocket, RocketOptions +from lib.models.rocket import Rocket from lib.models.motor import MotorKinds from lib.repositories.repo import Repository, RepositoryNotInitializedException @@ -55,7 +55,7 @@ async def find_rocket(self, rocket_id: str): async def delete_rocket(self, rocket_id: str): collection = self.get_collection() - await collection.delete_one({"rocket_id": rocket_id}) + await collection.delete_one({"_id": ObjectId(rocket_id)}) return self async def create_rocket(self): @@ -67,7 +67,6 @@ async def create_rocket(self): """ try: rocket_to_dict = self.rocket.dict() - rocket_to_dict["rocket_option"] = self.rocket.rocket_option.value rocket_to_dict["motor"][ "motor_kind" ] = self.rocket.motor.motor_kind.value @@ -83,25 +82,21 @@ async def create_rocket(self): f"Call to repositories.rocket.create_rocket completed for Rocket {self.rocket_id}" ) - async def get_rocket_by_id(self, rocket_id: str) -> Union[Rocket, None]: + async def get_rocket_by_id(self, rocket_id: str) -> Self: """ Gets a models.Rocket from the database Returns: - models.Rocket + self """ try: read_rocket = await self.find_rocket(rocket_id) - parsed_rocket = ( - Rocket.parse_obj(read_rocket) if read_rocket else None - ) - parsed_rocket.motor.set_motor_kind( - MotorKinds(read_rocket["motor"]["motor_kind"]) - ) - parsed_rocket.set_rocket_option( - RocketOptions(read_rocket["rocket_option"]) - ) - self.rocket = parsed_rocket + if read_rocket: + parsed_rocket = Rocket.parse_obj(read_rocket) + parsed_rocket.motor.set_motor_kind( + MotorKinds(read_rocket["motor"]["motor_kind"]) + ) + self.rocket = parsed_rocket except PyMongoError as e: raise e from e except RepositoryNotInitializedException as e: @@ -142,7 +137,6 @@ async def update_rocket_by_id(self, rocket_id: str): """ try: rocket_to_dict = self.rocket.dict() - rocket_to_dict["rocket_option"] = self.rocket.rocket_option.value rocket_to_dict["motor"][ "motor_kind" ] = self.rocket.motor.motor_kind.value diff --git a/lib/routes/environment.py b/lib/routes/environment.py index 38840b7..1eb84a6 100644 --- a/lib/routes/environment.py +++ b/lib/routes/environment.py @@ -2,14 +2,14 @@ Environment routes """ -from fastapi import APIRouter +from fastapi import APIRouter, Response from opentelemetry import trace from lib.views.environment import ( EnvSummary, EnvCreated, EnvUpdated, - EnvPickle, + EnvDeleted, ) from lib.models.environment import Env from lib.controllers.environment import EnvController @@ -66,19 +66,38 @@ async def update_env(env_id: str, env: Env) -> EnvUpdated: return await EnvController(env).update_env_by_id(env_id) -@router.get("/rocketpy/{env_id}") -async def read_rocketpy_env(env_id: str) -> EnvPickle: +@router.get( + "/rocketpy/{env_id}", + responses={ + 203: { + "description": "Binary file download", + "content": {"application/octet-stream": {}}, + } + }, + status_code=203, + response_class=Response, +) +async def read_rocketpy_env(env_id: str): """ - Loads rocketpy.environment as jsonpickle string + Loads rocketpy.environment as a dill binary ## Args ``` env_id: str ``` """ with tracer.start_as_current_span("read_rocketpy_env"): - return await EnvController.get_rocketpy_env_as_jsonpickle(env_id) - - -@router.get("/{env_id}/simulate", include_in_schema=False) + headers = { + 'Content-Disposition': f'attachment; filename="rocketpy_environment_{env_id}.dill"' + } + binary = await EnvController.get_rocketpy_env_binary(env_id) + return Response( + content=binary, + headers=headers, + media_type="application/octet-stream", + status_code=203, + ) + + +@router.get("/{env_id}/summary") async def simulate_env(env_id: str) -> EnvSummary: """ Loads rocketpy.environment simulation @@ -88,3 +107,15 @@ async def simulate_env(env_id: str) -> EnvSummary: """ with tracer.start_as_current_span("simulate_env"): return await EnvController.simulate_env(env_id) + + +@router.delete("/{env_id}") +async def delete_env(env_id: str) -> EnvDeleted: + """ + Deletes an environment + + ## Args + ``` env_id: str ``` + """ + with tracer.start_as_current_span("delete_env"): + return await EnvController(env_id).delete_env_by_id(env_id) diff --git a/lib/routes/flight.py b/lib/routes/flight.py index adf432f..7045e2c 100644 --- a/lib/routes/flight.py +++ b/lib/routes/flight.py @@ -2,19 +2,20 @@ Flight routes """ -from fastapi import APIRouter +from fastapi import APIRouter, Response from opentelemetry import trace from lib.views.flight import ( FlightSummary, FlightCreated, FlightUpdated, - FlightPickle, + FlightDeleted, ) from lib.models.environment import Env from lib.models.flight import Flight -from lib.models.rocket import Rocket, RocketOptions +from lib.models.rocket import Rocket from lib.models.motor import MotorKinds +from lib.views.flight import FlightView from lib.controllers.flight import FlightController router = APIRouter( @@ -32,7 +33,7 @@ @router.post("/") async def create_flight( - flight: Flight, rocket_option: RocketOptions, motor_kind: MotorKinds + flight: Flight, motor_kind: MotorKinds ) -> FlightCreated: """ Creates a new flight @@ -41,13 +42,12 @@ async def create_flight( ``` Flight object as JSON ``` """ with tracer.start_as_current_span("create_flight"): - flight.rocket.set_rocket_option(rocket_option) flight.rocket.motor.set_motor_kind(motor_kind) return await FlightController(flight).create_flight() @router.get("/{flight_id}") -async def read_flight(flight_id: str) -> Flight: +async def read_flight(flight_id: str) -> FlightView: """ Reads a flight @@ -58,17 +58,34 @@ async def read_flight(flight_id: str) -> Flight: return await FlightController.get_flight_by_id(flight_id) -@router.get("/rocketpy/{flight_id}") -async def read_rocketpy_flight(flight_id: str) -> FlightPickle: +@router.get( + "/rocketpy/{flight_id}", + responses={ + 203: { + "description": "Binary file download", + "content": {"application/octet-stream": {}}, + } + }, + status_code=203, + response_class=Response, +) +async def read_rocketpy_flight(flight_id: str): """ - Reads a rocketpy flight object + Loads rocketpy.flight as a dill binary ## Args - ``` flight_id: Flight ID ``` + ``` flight_id: str ``` """ with tracer.start_as_current_span("read_rocketpy_flight"): - return await FlightController.get_rocketpy_flight_as_jsonpickle( - flight_id + headers = { + 'Content-Disposition': f'attachment; filename="rocketpy_flight_{flight_id}.dill"' + } + binary = await FlightController.get_rocketpy_flight_binary(flight_id) + return Response( + content=binary, + headers=headers, + media_type="application/octet-stream", + status_code=203, ) @@ -93,7 +110,6 @@ async def update_flight_env(flight_id: str, env: Env) -> FlightUpdated: async def update_flight_rocket( flight_id: str, rocket: Rocket, - rocket_option: RocketOptions, motor_kind: MotorKinds, ) -> FlightUpdated: """ @@ -106,7 +122,6 @@ async def update_flight_rocket( ``` """ with tracer.start_as_current_span("update_flight_rocket"): - rocket.set_rocket_option(rocket_option) rocket.motor.set_motor_kind(motor_kind) return await FlightController.update_rocket_by_flight_id( flight_id, @@ -118,7 +133,6 @@ async def update_flight_rocket( async def update_flight( flight_id: str, flight: Flight, - rocket_option: RocketOptions, motor_kind: MotorKinds, ) -> FlightUpdated: """ @@ -131,12 +145,11 @@ async def update_flight( ``` """ with tracer.start_as_current_span("update_flight"): - flight.rocket.set_rocket_option(rocket_option) flight.rocket.motor.set_motor_kind(motor_kind) return await FlightController(flight).update_flight_by_id(flight_id) -@router.get("/{flight_id}/simulate", include_in_schema=False) +@router.get("/{flight_id}/summary") async def simulate_flight(flight_id: str) -> FlightSummary: """ Simulates a flight @@ -146,3 +159,15 @@ async def simulate_flight(flight_id: str) -> FlightSummary: """ with tracer.start_as_current_span("simulate_flight"): return await FlightController.simulate_flight(flight_id) + + +@router.delete("/{flight_id}") +async def delete_flight(flight_id: str) -> FlightDeleted: + """ + Deletes a flight + + ## Args + ``` flight_id: Flight ID ``` + """ + with tracer.start_as_current_span("delete_flight"): + return await FlightController.delete_flight_by_id(flight_id) diff --git a/lib/routes/motor.py b/lib/routes/motor.py index faa0f21..d63f39e 100644 --- a/lib/routes/motor.py +++ b/lib/routes/motor.py @@ -2,17 +2,18 @@ Motor routes """ -from fastapi import APIRouter +from fastapi import APIRouter, Response from opentelemetry import trace from lib.views.motor import ( MotorSummary, MotorCreated, MotorUpdated, - MotorPickle, + MotorDeleted, ) from lib.models.motor import Motor, MotorKinds from lib.controllers.motor import MotorController +from lib.views.motor import MotorView router = APIRouter( prefix="/motors", @@ -41,7 +42,7 @@ async def create_motor(motor: Motor, motor_kind: MotorKinds) -> MotorCreated: @router.get("/{motor_id}") -async def read_motor(motor_id: str) -> Motor: +async def read_motor(motor_id: str) -> MotorView: """ Reads a motor @@ -70,19 +71,38 @@ async def update_motor( return await MotorController(motor).update_motor_by_id(motor_id) -@router.get("/rocketpy/{motor_id}") -async def read_rocketpy_motor(motor_id: str) -> MotorPickle: +@router.get( + "/rocketpy/{motor_id}", + responses={ + 203: { + "description": "Binary file download", + "content": {"application/octet-stream": {}}, + } + }, + status_code=203, + response_class=Response, +) +async def read_rocketpy_motor(motor_id: str): """ - Reads a rocketpy motor + Loads rocketpy.motor as a dill binary ## Args - ``` motor_id: Motor ID ``` + ``` motor_id: str ``` """ with tracer.start_as_current_span("read_rocketpy_motor"): - return await MotorController.get_rocketpy_motor_as_jsonpickle(motor_id) - - -@router.get("/{motor_id}/simulate", include_in_schema=False) + headers = { + 'Content-Disposition': f'attachment; filename="rocketpy_motor_{motor_id}.dill"' + } + binary = await MotorController.get_rocketpy_motor_binary(motor_id) + return Response( + content=binary, + headers=headers, + media_type="application/octet-stream", + status_code=203, + ) + + +@router.get("/{motor_id}/summary") async def simulate_motor(motor_id: str) -> MotorSummary: """ Simulates a motor @@ -92,3 +112,15 @@ async def simulate_motor(motor_id: str) -> MotorSummary: """ with tracer.start_as_current_span("simulate_motor"): return await MotorController.simulate_motor(motor_id) + + +@router.delete("/{motor_id}") +async def delete_motor(motor_id: str) -> MotorDeleted: + """ + Deletes a motor + + ## Args + ``` motor_id: Motor ID ``` + """ + with tracer.start_as_current_span("delete_motor"): + return await MotorController.delete_motor_by_id(motor_id) diff --git a/lib/routes/rocket.py b/lib/routes/rocket.py index 48eecf8..e0b7a8a 100644 --- a/lib/routes/rocket.py +++ b/lib/routes/rocket.py @@ -2,17 +2,18 @@ Rocket routes """ -from fastapi import APIRouter +from fastapi import APIRouter, Response from opentelemetry import trace from lib.views.rocket import ( RocketSummary, RocketCreated, RocketUpdated, - RocketPickle, + RocketDeleted, ) -from lib.models.rocket import Rocket, RocketOptions +from lib.models.rocket import Rocket from lib.models.motor import MotorKinds +from lib.views.rocket import RocketView from lib.controllers.rocket import RocketController router = APIRouter( @@ -30,7 +31,7 @@ @router.post("/") async def create_rocket( - rocket: Rocket, rocket_option: RocketOptions, motor_kind: MotorKinds + rocket: Rocket, motor_kind: MotorKinds ) -> RocketCreated: """ Creates a new rocket @@ -39,13 +40,12 @@ async def create_rocket( ``` Rocket object as a JSON ``` """ with tracer.start_as_current_span("create_rocket"): - rocket.set_rocket_option(rocket_option) rocket.motor.set_motor_kind(motor_kind) return await RocketController(rocket).create_rocket() @router.get("/{rocket_id}") -async def read_rocket(rocket_id: str) -> Rocket: +async def read_rocket(rocket_id: str) -> RocketView: """ Reads a rocket @@ -60,7 +60,6 @@ async def read_rocket(rocket_id: str) -> Rocket: async def update_rocket( rocket_id: str, rocket: Rocket, - rocket_option: RocketOptions, motor_kind: MotorKinds, ) -> RocketUpdated: """ @@ -73,26 +72,42 @@ async def update_rocket( ``` """ with tracer.start_as_current_span("update_rocket"): - rocket.set_rocket_option(rocket_option) rocket.motor.set_motor_kind(motor_kind) return await RocketController(rocket).update_rocket_by_id(rocket_id) -@router.get("/rocketpy/{rocket_id}") -async def read_rocketpy_rocket(rocket_id: str) -> RocketPickle: +@router.get( + "/rocketpy/{rocket_id}", + responses={ + 203: { + "description": "Binary file download", + "content": {"application/octet-stream": {}}, + } + }, + status_code=203, + response_class=Response, +) +async def read_rocketpy_rocket(rocket_id: str): """ - Reads a rocketpy rocket + Loads rocketpy.rocket as a dill binary ## Args - ``` rocket_id: Rocket ID ``` + ``` rocket_id: str ``` """ with tracer.start_as_current_span("read_rocketpy_rocket"): - return await RocketController.get_rocketpy_rocket_as_jsonpickle( - rocket_id + headers = { + 'Content-Disposition': f'attachment; filename="rocketpy_rocket_{rocket_id}.dill"' + } + binary = await RocketController.get_rocketpy_rocket_binary(rocket_id) + return Response( + content=binary, + headers=headers, + media_type="application/octet-stream", + status_code=203, ) -@router.get("/{rocket_id}/simulate", include_in_schema=False) +@router.get("/{rocket_id}/summary") async def simulate_rocket(rocket_id: str) -> RocketSummary: """ Simulates a rocket @@ -102,3 +117,15 @@ async def simulate_rocket(rocket_id: str) -> RocketSummary: """ with tracer.start_as_current_span("simulate_rocket"): return await RocketController.simulate_rocket(rocket_id) + + +@router.delete("/{rocket_id}") +async def delete_rocket(rocket_id: str) -> RocketDeleted: + """ + Deletes a rocket + + ## Args + ``` rocket_id: Rocket ID ``` + """ + with tracer.start_as_current_span("delete_rocket"): + return await RocketController.delete_rocket_by_id(rocket_id) diff --git a/lib/services/environment.py b/lib/services/environment.py index e641a43..e52af0b 100644 --- a/lib/services/environment.py +++ b/lib/services/environment.py @@ -1,11 +1,18 @@ from typing import Self + +import dill + from rocketpy.environment.environment import Environment as RocketPyEnvironment from rocketpy.utilities import get_instance_attributes from lib.models.environment import Env from lib.views.environment import EnvSummary -class EnvironmentService(RocketPyEnvironment): +class EnvironmentService: + _environment: RocketPyEnvironment + + def __init__(self, environment: RocketPyEnvironment = None): + self._environment = environment @classmethod def from_env_model(cls, env: Env) -> Self: @@ -15,7 +22,7 @@ def from_env_model(cls, env: Env) -> Self: Returns: RocketPyEnvironment """ - rocketpy_env = cls( + rocketpy_env = RocketPyEnvironment( latitude=env.latitude, longitude=env.longitude, elevation=env.elevation, @@ -24,7 +31,15 @@ def from_env_model(cls, env: Env) -> Self: rocketpy_env.set_atmospheric_model( type=env.atmospheric_model_type, file=env.atmospheric_model_file ) - return rocketpy_env + return cls(environment=rocketpy_env) + + @property + def environment(self) -> RocketPyEnvironment: + return self._environment + + @environment.setter + def environment(self, environment: RocketPyEnvironment): + self._environment = environment def get_env_summary(self) -> EnvSummary: """ @@ -34,6 +49,15 @@ def get_env_summary(self) -> EnvSummary: EnvSummary """ - attributes = get_instance_attributes(self) + attributes = get_instance_attributes(self.environment) env_summary = EnvSummary(**attributes) return env_summary + + def get_env_binary(self) -> bytes: + """ + Get the binary representation of the environment. + + Returns: + bytes + """ + return dill.dumps(self.environment) diff --git a/lib/services/flight.py b/lib/services/flight.py index 15233f8..cdb4abe 100644 --- a/lib/services/flight.py +++ b/lib/services/flight.py @@ -1,5 +1,7 @@ from typing import Self +import dill + from rocketpy.simulation.flight import Flight as RocketPyFlight from rocketpy.utilities import get_instance_attributes @@ -9,7 +11,11 @@ from lib.views.flight import FlightSummary -class FlightService(RocketPyFlight): +class FlightService: + _flight: RocketPyFlight + + def __init__(self, flight: RocketPyFlight = None): + self._flight = flight @classmethod def from_flight_model(cls, flight: Flight) -> Self: @@ -17,10 +23,12 @@ def from_flight_model(cls, flight: Flight) -> Self: Get the rocketpy flight object. Returns: - RocketPyFlight + FlightService containing the rocketpy flight object. """ - rocketpy_rocket = RocketService.from_rocket_model(flight.rocket) - rocketpy_env = EnvironmentService.from_env_model(flight.environment) + rocketpy_env = EnvironmentService.from_env_model( + flight.environment + ).environment + rocketpy_rocket = RocketService.from_rocket_model(flight.rocket).rocket rocketpy_flight = RocketPyFlight( rocket=rocketpy_rocket, inclination=flight.inclination, @@ -28,7 +36,15 @@ def from_flight_model(cls, flight: Flight) -> Self: environment=rocketpy_env, rail_length=flight.rail_length, ) - return rocketpy_flight + return cls(flight=rocketpy_flight) + + @property + def flight(self) -> RocketPyFlight: + return self._flight + + @flight.setter + def flight(self, flight: RocketPyFlight): + self._flight = flight def get_flight_summary(self) -> FlightSummary: """ @@ -37,6 +53,15 @@ def get_flight_summary(self) -> FlightSummary: Returns: FlightSummary """ - attributes = get_instance_attributes(self) + attributes = get_instance_attributes(self.flight) flight_summary = FlightSummary(**attributes) return flight_summary + + def get_flight_binary(self) -> bytes: + """ + Get the binary representation of the flight. + + Returns: + bytes + """ + return dill.dumps(self.flight) diff --git a/lib/services/motor.py b/lib/services/motor.py index ee60a8e..c28f486 100644 --- a/lib/services/motor.py +++ b/lib/services/motor.py @@ -1,13 +1,29 @@ from typing import Self + +import dill + +from rocketpy.motors.motor import GenericMotor, Motor as RocketPyMotor from rocketpy.motors.solid_motor import SolidMotor from rocketpy.motors.liquid_motor import LiquidMotor from rocketpy.motors.hybrid_motor import HybridMotor from rocketpy.utilities import get_instance_attributes -from lib.models.motor import Motor, MotorKinds +from rocketpy import ( + LevelBasedTank, + MassBasedTank, + MassFlowRateBasedTank, + UllageBasedTank, + TankGeometry, +) + +from lib.models.motor import Motor, MotorKinds, TankKinds from lib.views.motor import MotorSummary class MotorService: + _motor: RocketPyMotor + + def __init__(self, motor: RocketPyMotor = None): + self._motor = motor @classmethod def from_motor_model(cls, motor: Motor) -> Self: @@ -15,29 +31,30 @@ def from_motor_model(cls, motor: Motor) -> Self: Get the rocketpy motor object. Returns: - Mixin of rocketpy motor and MotorService + MotorService containing the rocketpy motor object. """ motor_core = { - "thrust_source": ( - f"lib/data/engines/{motor.thrust_source.value}.eng" - ), + "thrust_source": motor.thrust_source, "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, + "coordinate_system_orientation": ( + motor.coordinate_system_orientation.value.lower() + if motor.coordinate_system_orientation + else None + ), + "interpolation_method": motor.interpolation_method, + "reshape_thrust_curve": motor.reshape_thrust_curve, } match motor.motor_kind: case MotorKinds.LIQUID: - rocketpy_motor = type( - "LiquidMotorMixin", (LiquidMotor, cls), {} - )(**motor_core) + rocketpy_motor = LiquidMotor(**motor_core) case MotorKinds.HYBRID: - rocketpy_motor = type( - "HybridMotorMixin", (HybridMotor, cls), {} - )( + rocketpy_motor = HybridMotor( **motor_core, throat_radius=motor.throat_radius, grain_number=motor.grain_number, @@ -48,10 +65,8 @@ def from_motor_model(cls, motor: Motor) -> Self: grain_separation=motor.grain_separation, grains_center_of_mass_position=motor.grains_center_of_mass_position, ) - case _: - rocketpy_motor = type( - "SolidMotorMixin", (SolidMotor, cls), {} - )( + case MotorKinds.SOLID: + rocketpy_motor = SolidMotor( **motor_core, grain_number=motor.grain_number, grain_density=motor.grain_density, @@ -60,15 +75,66 @@ def from_motor_model(cls, motor: Motor) -> Self: 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, + ) + case _: + rocketpy_motor = GenericMotor( + **motor_core, + chamber_radius=motor.chamber_radius, + chamber_height=motor.chamber_height, + chamber_position=motor.chamber_position, + propellant_initial_mass=motor.propellant_initial_mass, + nozzle_position=motor.nozzle_position, ) - if motor.motor_kind != MotorKinds.SOLID: + if motor.motor_kind not in (MotorKinds.SOLID, MotorKinds.GENERIC): for tank in motor.tanks: - rocketpy_motor.add_tank(tank.tank, tank.position) + tank_core = { + "name": tank.name, + "geometry": TankGeometry( + geometry_dict=dict(tank.geometry) + ), + "flux_time": tank.flux_time, + "gas": tank.gas, + "liquid": tank.liquid, + "discretize": tank.discretize, + } + + match tank.tank_kind: + case TankKinds.LEVEL: + rocketpy_tank = LevelBasedTank( + **tank_core, liquid_height=tank.liquid_height + ) + case TankKinds.MASS: + rocketpy_tank = MassBasedTank( + **tank_core, + liquid_mass=tank.liquid_mass, + gas_mass=tank.gas_mass, + ) + case TankKinds.MASS_FLOW: + rocketpy_tank = MassFlowRateBasedTank( + **tank_core, + gas_mass_flow_rate_in=tank.gas_mass_flow_rate_in, + gas_mass_flow_rate_out=tank.gas_mass_flow_rate_out, + liquid_mass_flow_rate_in=tank.liquid_mass_flow_rate_in, + liquid_mass_flow_rate_out=tank.liquid_mass_flow_rate_out, + initial_liquid_mass=tank.initial_liquid_mass, + initial_gas_mass=tank.initial_gas_mass, + ) + case TankKinds.ULLAGE: + rocketpy_tank = UllageBasedTank( + **tank_core, ullage=tank.ullage + ) + rocketpy_motor.add_tank(rocketpy_tank, tank.position) + + return cls(motor=rocketpy_motor) - return rocketpy_motor + @property + def motor(self) -> RocketPyMotor: + return self._motor + + @motor.setter + def motor(self, motor: RocketPyMotor): + self._motor = motor def get_motor_summary(self) -> MotorSummary: """ @@ -77,6 +143,15 @@ def get_motor_summary(self) -> MotorSummary: Returns: MotorSummary """ - attributes = get_instance_attributes(self) + attributes = get_instance_attributes(self.motor) motor_summary = MotorSummary(**attributes) return motor_summary + + def get_motor_binary(self) -> bytes: + """ + Get the binary representation of the motor. + + Returns: + bytes + """ + return dill.dumps(self.motor) diff --git a/lib/services/rocket.py b/lib/services/rocket.py index d3cd527..a9e38fa 100644 --- a/lib/services/rocket.py +++ b/lib/services/rocket.py @@ -1,22 +1,31 @@ import ast from typing import Self + +import dill + from rocketpy.rocket.rocket import Rocket as RocketPyRocket from rocketpy.rocket.parachute import Parachute as RocketPyParachute -from rocketpy.rocket.aero_surface import NoseCone as RocketPyNoseCone from rocketpy.rocket.aero_surface import ( TrapezoidalFins as RocketPyTrapezoidalFins, + EllipticalFins as RocketPyEllipticalFins, + NoseCone as RocketPyNoseCone, + Fins as RocketPyFins, + Tail as RocketPyTail, ) -from rocketpy.rocket.aero_surface import Tail as RocketPyTail from rocketpy.utilities import get_instance_attributes -from lib.models.rocket import Rocket -from lib.models.aerosurfaces import NoseCone, TrapezoidalFins, Tail -from lib.models.parachute import Parachute +from lib import logger +from lib.models.rocket import Rocket, Parachute +from lib.models.aerosurfaces import NoseCone, Tail, Fins from lib.services.motor import MotorService from lib.views.rocket import RocketSummary -class RocketService(RocketPyRocket): +class RocketService: + _rocket: RocketPyRocket + + def __init__(self, rocket: RocketPyRocket = None): + self._rocket = rocket @classmethod def from_rocket_model(cls, rocket: Rocket) -> Self: @@ -24,17 +33,22 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: Get the rocketpy rocket object. Returns: - RocketPyRocket + RocketService containing the rocketpy rocket object. """ + # Core rocketpy_rocket = RocketPyRocket( radius=rocket.radius, mass=rocket.mass, inertia=rocket.inertia, - power_off_drag=rocket.power_off_drag, - power_on_drag=rocket.power_on_drag, + power_off_drag=( + rocket.power_off_drag if rocket.power_off_drag else None + ), + power_on_drag=( + rocket.power_on_drag if rocket.power_on_drag else None + ), center_of_mass_without_motor=rocket.center_of_mass_without_motor, - coordinate_system_orientation=rocket.coordinate_system_orientation, + coordinate_system_orientation=rocket.coordinate_system_orientation.value.lower(), ) # RailButtons @@ -44,7 +58,7 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: angular_position=rocket.rail_buttons.angular_position, ) rocketpy_rocket.add_motor( - MotorService.from_motor_model(rocket.motor), + MotorService.from_motor_model(rocket.motor).motor, rocket.motor_position, ) @@ -54,9 +68,7 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: rocketpy_rocket.evaluate_static_margin() # FinSet - # TODO: re-write this to match overall fins not only TrapezoidalFins - # Maybe a strategy with different factory methods? - finset = cls.get_rocketpy_finset(rocket.fins) + finset = cls.get_rocketpy_finset(rocket.fins, rocket.fins.fins_kind) rocketpy_rocket.aerodynamic_surfaces.add(finset, finset.position) rocketpy_rocket.evaluate_static_margin() @@ -65,20 +77,33 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: rocketpy_rocket.aerodynamic_surfaces.add(tail, tail.position) rocketpy_rocket.evaluate_static_margin() + # Air Brakes + # Parachutes - for p, _ in enumerate(rocket.parachutes): - parachute_trigger = rocket.parachutes[p].triggers[0] - if cls.check_parachute_trigger(parachute_trigger): - rocket.parachutes[p].triggers[0] = compile( - parachute_trigger, "", "eval" + for parachute in rocket.parachutes: + if cls.check_parachute_trigger( + trigger_expression := parachute.trigger + ): + parachute.trigger = eval( # pylint: disable=eval-used + trigger_expression, {"__builtins__": None}, {} ) - parachute = cls.get_rocketpy_parachute(rocket.parachutes, p) - rocketpy_rocket.parachutes.append(parachute) + rocketpy_parachute = cls.get_rocketpy_parachute(parachute) + rocketpy_rocket.parachutes.append(rocketpy_parachute) else: - print("Parachute trigger not valid. Skipping parachute.") + logger.warning( + "Parachute trigger not valid. Skipping parachute." + ) continue - return rocketpy_rocket + return cls(rocket=rocketpy_rocket) + + @property + def rocket(self) -> RocketPyRocket: + return self._rocket + + @rocket.setter + def rocket(self, rocket: RocketPyRocket): + self._rocket = rocket def get_rocket_summary(self) -> RocketSummary: """ @@ -87,10 +112,19 @@ def get_rocket_summary(self) -> RocketSummary: Returns: RocketSummary """ - attributes = get_instance_attributes(self) + attributes = get_instance_attributes(self.rocket) rocket_summary = RocketSummary(**attributes) return rocket_summary + def get_rocket_binary(self) -> bytes: + """ + Get the binary representation of the rocket. + + Returns: + bytes + """ + return dill.dumps(self.rocket) + @staticmethod def get_rocketpy_nose(nose: NoseCone) -> RocketPyNoseCone: """ @@ -101,6 +135,7 @@ def get_rocketpy_nose(nose: NoseCone) -> RocketPyNoseCone: """ rocketpy_nose = RocketPyNoseCone( + name=nose.name, length=nose.length, kind=nose.kind, base_radius=nose.base_radius, @@ -110,25 +145,48 @@ def get_rocketpy_nose(nose: NoseCone) -> RocketPyNoseCone: return rocketpy_nose @staticmethod - def get_rocketpy_finset( - trapezoidal_fins: TrapezoidalFins, - ) -> RocketPyTrapezoidalFins: + def get_rocketpy_finset(fins: Fins, kind: str) -> RocketPyFins: """ Get a rocketpy finset object. - Returns: + Returns one of: RocketPyTrapezoidalFins + RocketPyEllipticalFins """ - rocketpy_finset = RocketPyTrapezoidalFins( - n=trapezoidal_fins.n, - root_chord=trapezoidal_fins.root_chord, - tip_chord=trapezoidal_fins.tip_chord, - span=trapezoidal_fins.span, - cant_angle=trapezoidal_fins.cant_angle, - rocket_radius=trapezoidal_fins.radius, - airfoil=trapezoidal_fins.airfoil, - ) - rocketpy_finset.position = trapezoidal_fins.position + match kind: + case "TRAPEZOIDAL": + rocketpy_finset = RocketPyTrapezoidalFins( + n=fins.n, + name=fins.name, + root_chord=fins.root_chord, + tip_chord=fins.tip_chord, + span=fins.span, + cant_angle=fins.cant_angle, + rocket_radius=fins.radius, + airfoil=fins.airfoil, + ) + case "ELLIPTICAL": + rocketpy_finset = RocketPyEllipticalFins( + n=fins.n, + name=fins.name, + root_chord=fins.root_chord, + span=fins.span, + cant_angle=fins.cant_angle, + rocket_radius=fins.radius, + airfoil=fins.airfoil, + ) + case _: + rocketpy_finset = RocketPyTrapezoidalFins( + n=fins.n, + name=fins.name, + tip_chord=fins.tip_chord, + root_chord=fins.root_chord, + span=fins.span, + cant_angle=fins.cant_angle, + rocket_radius=fins.radius, + airfoil=fins.airfoil, + ) + rocketpy_finset.position = fins.position return rocketpy_finset @staticmethod @@ -140,6 +198,7 @@ def get_rocketpy_tail(tail: Tail) -> RocketPyTail: RocketPyTail """ rocketpy_tail = RocketPyTail( + name=tail.name, top_radius=tail.top_radius, bottom_radius=tail.bottom_radius, length=tail.length, @@ -149,9 +208,7 @@ def get_rocketpy_tail(tail: Tail) -> RocketPyTail: return rocketpy_tail @staticmethod - def get_rocketpy_parachute( - parachute: Parachute, p: int - ) -> RocketPyParachute: + def get_rocketpy_parachute(parachute: Parachute) -> RocketPyParachute: """ Get a rocketpy parachute object. @@ -159,12 +216,12 @@ def get_rocketpy_parachute( RocketPyParachute """ rocketpy_parachute = RocketPyParachute( - name=parachute[p].name[0], - cd_s=parachute[p].cd_s[0], - trigger=eval(parachute[p].triggers[0]), - sampling_rate=parachute[p].sampling_rate[0], - lag=parachute[p].lag[0], - noise=parachute[p].noise[0], + name=parachute.name, + cd_s=parachute.cd_s, + trigger=parachute.trigger, + sampling_rate=parachute.sampling_rate, + lag=parachute.lag, + noise=parachute.noise, ) return rocketpy_parachute @@ -180,12 +237,16 @@ def check_parachute_trigger(expression: str) -> bool: bool: True if the expression is valid, False otherwise. """ + class InvalidParachuteTrigger(Exception): + pass + # Parsing the expression into an AST try: parsed_expression = ast.parse(expression, mode="eval") - except SyntaxError: - print("Invalid syntax.") - return False + except SyntaxError as e: + raise InvalidParachuteTrigger( + f"Invalid expression syntax: {str(e)}" + ) from None # Constant case (supported after beta v1) if isinstance(parsed_expression.body, ast.Constant): @@ -195,36 +256,40 @@ def check_parachute_trigger(expression: str) -> bool: isinstance(parsed_expression.body, ast.Name) and parsed_expression.body.id == "apogee" ): - global apogee - apogee = "apogee" return True # Validating the expression structure if not isinstance(parsed_expression.body, ast.Lambda): - print("Invalid expression structure (not a Lambda).") - return False + raise InvalidParachuteTrigger( + "Invalid expression structure: not a lambda." + ) from None lambda_node = parsed_expression.body if len(lambda_node.args.args) != 3: - print("Invalid expression structure (invalid arity).") - return False + raise InvalidParachuteTrigger( + "Invalid expression structure: lambda should have 3 arguments." + ) from None if not isinstance(lambda_node.body, ast.Compare): try: for operand in lambda_node.body.values: if not isinstance(operand, ast.Compare): - print("Invalid expression structure (not a Compare).") - return False + raise InvalidParachuteTrigger( + "Invalid expression structure: not a Compare." + ) from None except AttributeError: - print("Invalid expression structure (not a Compare).") - return False + raise InvalidParachuteTrigger( + "Invalid expression structure: not a Compare." + ) from None # Restricting access to functions or attributes for node in ast.walk(lambda_node): if isinstance(node, ast.Call): - print("Calling functions is not allowed in the expression.") - return False + raise InvalidParachuteTrigger( + "Calling functions is not allowed in the expression." + ) from None if isinstance(node, ast.Attribute): - print("Accessing attributes is not allowed in the expression.") - return False + raise InvalidParachuteTrigger( + "Accessing attributes is not allowed in the expression." + ) from None return True diff --git a/lib/settings/gunicorn.py b/lib/settings/gunicorn.py index 6f75825..34e398c 100644 --- a/lib/settings/gunicorn.py +++ b/lib/settings/gunicorn.py @@ -7,7 +7,7 @@ def post_fork(server, worker): # pylint: disable=unused-argument uptrace.configure_opentelemetry( dsn=Secrets.get_secret("UPTRACE_DSN"), service_name="infinity-api", - service_version="1.2.1", + service_version="2.0.0", deployment_environment="production", ) from lib.api import ( # pylint: disable=import-outside-toplevel diff --git a/lib/utils.py b/lib/utils.py new file mode 100644 index 0000000..666d1c2 --- /dev/null +++ b/lib/utils.py @@ -0,0 +1,128 @@ +# fork of https://github.com/encode/starlette/blob/master/starlette/middleware/gzip.py +import gzip +import io +import typing + +from starlette.datastructures import Headers, MutableHeaders +from starlette.types import ASGIApp, Message, Receive, Scope, Send + + +class RocketPyGZipMiddleware: + def __init__( + self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9 + ) -> None: + self.app = app + self.minimum_size = minimum_size + self.compresslevel = compresslevel + + async def __call__( + self, scope: Scope, receive: Receive, send: Send + ) -> None: + if scope["type"] == "http": + headers = Headers(scope=scope) + if "gzip" in headers.get("Accept-Encoding", ""): + responder = GZipResponder( + self.app, + self.minimum_size, + compresslevel=self.compresslevel, + ) + await responder(scope, receive, send) + return + await self.app(scope, receive, send) + + +class GZipResponder: + def __init__( + self, app: ASGIApp, minimum_size: int, compresslevel: int = 9 + ) -> None: + self.app = app + self.minimum_size = minimum_size + self.send: Send = unattached_send + self.initial_message: Message = {} + self.started = False + self.content_encoding_set = False + self.gzip_buffer = io.BytesIO() + self.gzip_file = gzip.GzipFile( + mode="wb", fileobj=self.gzip_buffer, compresslevel=compresslevel + ) + + async def __call__( + self, scope: Scope, receive: Receive, send: Send + ) -> None: + self.send = send + with self.gzip_buffer, self.gzip_file: + await self.app(scope, receive, self.send_with_gzip) + + async def send_with_gzip(self, message: Message) -> None: + message_type = message["type"] + if message_type == "http.response.start": + # Don't send the initial message until we've determined how to + # modify the outgoing headers correctly. + self.initial_message = message + headers = Headers(raw=self.initial_message["headers"]) + self.content_encoding_set = "content-encoding" in headers + elif ( + message_type == "http.response.body" and self.content_encoding_set + ): + if not self.started: + self.started = True + await self.send(self.initial_message) + await self.send(message) + elif message_type == "http.response.body" and not self.started: + self.started = True + body = message.get("body", b"") + more_body = message.get("more_body", False) + if len(body) < (self.minimum_size and not more_body) or any( + value == b'application/octet-stream' + for header, value in self.initial_message["headers"] + ): + # Don't apply GZip to small outgoing responses or octet-streams. + await self.send(self.initial_message) + await self.send(message) + elif not more_body: + # Standard GZip response. + self.gzip_file.write(body) + self.gzip_file.close() + body = self.gzip_buffer.getvalue() + + headers = MutableHeaders(raw=self.initial_message["headers"]) + headers["Content-Encoding"] = "gzip" + headers["Content-Length"] = str(len(body)) + headers.add_vary_header("Accept-Encoding") + message["body"] = body + + await self.send(self.initial_message) + await self.send(message) + else: + # Initial body in streaming GZip response. + headers = MutableHeaders(raw=self.initial_message["headers"]) + headers["Content-Encoding"] = "gzip" + headers.add_vary_header("Accept-Encoding") + del headers["Content-Length"] + + self.gzip_file.write(body) + message["body"] = self.gzip_buffer.getvalue() + self.gzip_buffer.seek(0) + self.gzip_buffer.truncate() + + await self.send(self.initial_message) + await self.send(message) + + elif message_type == "http.response.body": + # Remaining body in streaming GZip response. + body = message.get("body", b"") + more_body = message.get("more_body", False) + + self.gzip_file.write(body) + if not more_body: + self.gzip_file.close() + + message["body"] = self.gzip_buffer.getvalue() + self.gzip_buffer.seek(0) + self.gzip_buffer.truncate() + + await self.send(message) + + +async def unattached_send(message: Message) -> typing.NoReturn: + raise RuntimeError("send awaitable not set") # pragma: no cover diff --git a/lib/views/environment.py b/lib/views/environment.py index b26b115..f6c59b1 100644 --- a/lib/views/environment.py +++ b/lib/views/environment.py @@ -1,49 +1,49 @@ +from typing import Any, Optional from pydantic import BaseModel class EnvSummary(BaseModel): - latitude: float - longitude: float - elevation: float - # date: str #datetime - atmospheric_model_type: str - air_gas_constant: float - standard_g: float - earth_radius: float - datum: str - timezone: str - # ellipsoid: str # function - initial_utm_zone: int - initial_utm_letter: str - initial_north: float - initial_east: float - initial_hemisphere: str - initial_ew: str - # local_date: str #datetime - # datetime_date: str #datetime - max_expected_height: int - # barometric_height: str # function - # barometric_height_ISA: str # function - # pressure: str # function - # pressure_ISA: str # function - # temperature: str # function - # temperature_ISA: str # function - # density: str # function - # speed_of_sound: str # function - # dynamic_viscosity: str # function - # gravity: str # function - # somigliana_gravity: str # function - # wind_speed: str # function - # wind_direction: str # function - # wind_heading: str # function - # wind_velocity_x: str # function - # wind_velocity_y: str # function - # calculate_earth_radius: str # function - # decimal_degrees_to_arc_seconds: str # function - # geodesic_to_utm: str # function - # utm_to_geodesic: str # function - # prints: str # function - # plots: str # function + # TODO: if Any is Callable, jumps pydantic parsing, expects a dill binary object + latitude: Optional[float] + longitude: Optional[float] + elevation: Optional[float] + atmospheric_model_type: Optional[str] + air_gas_constant: Optional[float] + standard_g: Optional[float] + earth_radius: Optional[float] + datum: Optional[str] + timezone: Optional[str] + initial_utm_zone: Optional[int] + initial_utm_letter: Optional[str] + initial_north: Optional[float] + initial_east: Optional[float] + initial_hemisphere: Optional[str] + initial_ew: Optional[str] + max_expected_height: Optional[int] + date: Optional[Any] + ellipsoid: Optional[Any] + local_date: Optional[Any] + datetime_date: Optional[Any] + barometric_height: Optional[Any] + barometric_height_ISA: Optional[Any] + pressure: Optional[Any] + pressure_ISA: Optional[Any] + temperature: Optional[Any] + temperature_ISA: Optional[Any] + density: Optional[Any] + speed_of_sound: Optional[Any] + dynamic_viscosity: Optional[Any] + gravity: Optional[Any] + somigliana_gravity: Optional[Any] + wind_speed: Optional[Any] + wind_direction: Optional[Any] + wind_heading: Optional[Any] + wind_velocity_x: Optional[Any] + wind_velocity_y: Optional[Any] + calculate_earth_radius: Optional[Any] + decimal_degrees_to_arc_seconds: Optional[Any] + geodesic_to_utm: Optional[Any] + utm_to_geodesic: Optional[Any] class EnvCreated(BaseModel): @@ -59,7 +59,3 @@ class EnvUpdated(BaseModel): class EnvDeleted(BaseModel): env_id: str message: str = "Environment successfully deleted" - - -class EnvPickle(BaseModel): - jsonpickle_rocketpy_env: str diff --git a/lib/views/flight.py b/lib/views/flight.py index ace8b00..1c4dd3c 100644 --- a/lib/views/flight.py +++ b/lib/views/flight.py @@ -1,109 +1,159 @@ from typing import Optional, Any from pydantic import BaseModel - - -class InitialConditions(BaseModel): - initial_position: str - initial_velocity: str - initial_altitude: str - initial_angular_position: str - initial_angular_velocity: str - - -class NumericalIntegrationSettings(BaseModel): - max_time: str - max_time_step: str - min_time_step: str - relative_error_tolerance: str - absolute_error_tolerance: str - time_overshoot: str - terminate_on_apogee: str - number_of_time_steps: str - function_evaluations_per_time_step: str - avg_function_evaluations_per_time_step: str - - -class SurfaceWindConditions(BaseModel): - frontal_surface_wind_speed: str - lateral_surface_wind_speed: str - - -class LaunchRailConditions(BaseModel): - rail_length: str - flight_inclination: str - flight_heading: str - - -class OutOfRailConditions(BaseModel): - out_of_rail_time: str - out_of_rail_velocity: str - out_of_rail_static_margin: str - out_of_rail_angle_of_attack: str - out_of_rail_thrust_weight_ratio: str - out_of_rail_reynolds_number: str - - -class BurnoutConditions(BaseModel): - burnout_time: str - burnout_rocket_velocity: str - burnout_altitude: str - burnout_freestream_velocity: str - burnout_mach_number: str - burnout_kinetic_energy: str - - -class ApogeeConditions(BaseModel): - apogee_time: str - apogee_altitude: str - apogee_freestream_speed: str - - -class MaximumValues(BaseModel): - maximum_speed: str - maximum_mach_number: str - maximum_reynolds_number: str - maximum_dynamic_pressure: str - maximum_acceleration_during_motor_burn: str - maximum_acceleration_after_motor_burn: str - maximum_gs_during_motor_burn: str - maximum_gs_after_motor_burn: str - maximum_upper_rail_button_normal_force: str - maximum_upper_rail_button_shear_force: str - maximum_lower_rail_button_normal_force: str - maximum_lower_rail_button_shear_force: str - - -class ImpactConditions(BaseModel): - x_impact_position: "Optional[str]" - y_impact_position: "Optional[str]" - time_of_impact: "Optional[str]" - impact_velocity: "Optional[str]" - - -class EventsRegistered(BaseModel): - events_trace: "Optional[Any]" - - -class FlightData(BaseModel): - initial_conditions: InitialConditions - numerical_integration_settings: NumericalIntegrationSettings - launch_rail_conditions: LaunchRailConditions - surface_wind_conditions: SurfaceWindConditions - out_of_rail_conditions: OutOfRailConditions - burnout_conditions: BurnoutConditions - apogee_conditions: ApogeeConditions - maximum_values: MaximumValues - impact_conditions: ImpactConditions - events_registered: "Optional[EventsRegistered]" - - -class FlightPlots(BaseModel): - pass - - -class FlightSummary(BaseModel): - flight_data: FlightData - # flight_plots: FlightPlots +from lib.models.flight import Flight +from lib.views.rocket import RocketView, RocketSummary +from lib.views.environment import EnvSummary + + +class FlightSummary(RocketSummary, EnvSummary): + # TODO: if Any is Callable, jumps pydantic parsing, expects a dill binary object + # TODO: implement {flight_id}/summary/motor; {flight_id}/summary/rocket; {flight_id}/summary/environment + name: Optional[str] + max_time: Optional[int] + min_time_step: Optional[int] + # max_time_step: Optional[float] + equations_of_motion: Optional[str] + heading: Optional[int] + inclination: Optional[int] + initial_solution: Optional[list] + effective_1rl: Optional[float] + effective_2rl: Optional[float] + out_of_rail_time: Optional[float] + out_of_rail_time_index: Optional[int] + parachute_cd_s: Optional[float] + post_processed: Optional[bool] + rail_length: Optional[float] + rtol: Optional[float] + t: Optional[float] + t_final: Optional[float] + t_initial: Optional[int] + terminate_on_apogee: Optional[bool] + time_overshoot: Optional[bool] + latitude: Optional[Any] + longitude: Optional[Any] + M1: Optional[Any] + M2: Optional[Any] + M3: Optional[Any] + R1: Optional[Any] + R2: Optional[Any] + R3: Optional[Any] + acceleration: Optional[Any] + aerodynamic_bending_moment: Optional[Any] + aerodynamic_drag: Optional[Any] + aerodynamic_lift: Optional[Any] + aerodynamic_spin_moment: Optional[Any] + alpha1: Optional[Any] + alpha2: Optional[Any] + alpha3: Optional[Any] + altitude: Optional[Any] + angle_of_attack: Optional[Any] + apogee: Optional[Any] + apogee_freestream_speed: Optional[Any] + # apogee_state: Optional[Any] + apogee_time: Optional[Any] + apogee_x: Optional[Any] + apogee_y: Optional[Any] + atol: Optional[Any] + attitude_angle: Optional[Any] + attitude_frequency_response: Optional[Any] + attitude_vector_x: Optional[Any] + attitude_vector_y: Optional[Any] + attitude_vector_z: Optional[Any] + ax: Optional[Any] + ay: Optional[Any] + az: Optional[Any] + bearing: Optional[Any] + drag_power: Optional[Any] + drift: Optional[Any] + dynamic_pressure: Optional[Any] + e0: Optional[Any] + e1: Optional[Any] + e2: Optional[Any] + e3: Optional[Any] + free_stream_speed: Optional[Any] + frontal_surface_wind: Optional[Any] + function_evaluations: Optional[Any] + function_evaluations_per_time_step: Optional[Any] + horizontal_speed: Optional[Any] + # impact_state: Optional[Any] + impact_velocity: Optional[Any] + initial_stability_margin: Optional[Any] + kinetic_energy: Optional[Any] + lateral_attitude_angle: Optional[Any] + lateral_surface_wind: Optional[Any] + mach_number: Optional[Any] + max_acceleration: Optional[Any] + max_acceleration_power_off: Optional[Any] + max_acceleration_power_off_time: Optional[Any] + max_acceleration_power_on: Optional[Any] + max_acceleration_power_on_time: Optional[Any] + max_acceleration_time: Optional[Any] + max_dynamic_pressure: Optional[Any] + max_dynamic_pressure_time: Optional[Any] + max_mach_number: Optional[Any] + max_mach_number_time: Optional[Any] + max_rail_button1_normal_force: Optional[Any] + max_rail_button1_shear_force: Optional[Any] + max_rail_button2_normal_force: Optional[Any] + max_rail_button2_shear_force: Optional[Any] + max_reynolds_number: Optional[Any] + max_reynolds_number_time: Optional[Any] + max_speed: Optional[Any] + max_speed_time: Optional[Any] + max_stability_margin: Optional[Any] + max_stability_margin_time: Optional[Any] + max_total_pressure: Optional[Any] + max_total_pressure_time: Optional[Any] + min_stability_margin: Optional[Any] + min_stability_margin_time: Optional[Any] + omega1_frequency_response: Optional[Any] + omega2_frequency_response: Optional[Any] + omega3_frequency_response: Optional[Any] + out_of_rail_stability_margin: Optional[Any] + # out_of_rail_state: Optional[Any] + out_of_rail_velocity: Optional[Any] + # parachute_events: Optional[Any] + path_angle: Optional[Any] + phi: Optional[Any] + potential_energy: Optional[Any] + psi: Optional[Any] + rail_button1_normal_force: Optional[Any] + rail_button1_shear_force: Optional[Any] + rail_button2_normal_force: Optional[Any] + rail_button2_shear_force: Optional[Any] + reynolds_number: Optional[Any] + rotational_energy: Optional[Any] + solution: Optional[Any] + # solution_array: Optional[Any] + speed: Optional[Any] + stability_margin: Optional[Any] + static_margin: Optional[Any] + stream_velocity_x: Optional[Any] + stream_velocity_y: Optional[Any] + stream_velocity_z: Optional[Any] + theta: Optional[Any] + thrust_power: Optional[Any] + # time: Optional[Any] + # time_steps: Optional[Any] + total_energy: Optional[Any] + total_pressure: Optional[Any] + translational_energy: Optional[Any] + vx: Optional[Any] + vy: Optional[Any] + vz: Optional[Any] + w1: Optional[Any] + w2: Optional[Any] + w3: Optional[Any] + x: Optional[Any] + x_impact: Optional[Any] + y: Optional[Any] + y_impact: Optional[Any] + # y_sol: Optional[Any] + z: Optional[Any] + z_impact: Optional[Any] + # flight_phases: Optional[Any] + # FlightPhases: Optional[Any] + # TimeNodes: Optional[Any] class FlightCreated(BaseModel): @@ -121,5 +171,5 @@ class FlightDeleted(BaseModel): message: str = "Flight successfully deleted" -class FlightPickle(BaseModel): - jsonpickle_rocketpy_flight: str +class FlightView(Flight): + rocket: RocketView diff --git a/lib/views/motor.py b/lib/views/motor.py index 74cf6b2..9acd881 100644 --- a/lib/views/motor.py +++ b/lib/views/motor.py @@ -1,23 +1,72 @@ -from typing import List, Any +from typing import List, Any, Optional from pydantic import BaseModel +from lib.models.motor import Motor, MotorKinds class MotorSummary(BaseModel): - total_burning_time: str - total_propellant_mass: str - average_propellant_exhaust_velocity: str - average_thrust: str - maximum_thrust: str - total_impulse: str - thrust: List[Any] - total_mass: List[Any] - center_of_mass: List[Any] - i_11: List[Any] - i_22: List[Any] - i_33: List[Any] - i_12: List[Any] - i_13: List[Any] - i_23: List[Any] + # TODO: if Any is Callable, jumps pydantic parsing, expects a dill binary object + average_thrust: Optional[float] + burn_duration: Optional[float] + burn_out_time: Optional[float] + burn_start_time: Optional[float] + center_of_dry_mass_position: Optional[float] + coordinate_system_orientation: Optional[str] + dry_I_11: Optional[float] + dry_I_12: Optional[float] + dry_I_13: Optional[float] + dry_I_22: Optional[float] + dry_I_23: Optional[float] + dry_I_33: Optional[float] + dry_mass: Optional[float] + grain_burn_out: Optional[float] + grain_density: Optional[float] + grain_initial_height: Optional[float] + grain_initial_inner_radius: Optional[float] + grain_initial_mass: Optional[float] + grain_initial_volume: Optional[float] + grain_number: Optional[int] + grain_outer_radius: Optional[float] + grain_separation: Optional[float] + grains_center_of_mass_position: Optional[float] + interpolate: Optional[str] + max_thrust: Optional[float] + max_thrust_time: Optional[float] + nozzle_position: Optional[float] + nozzle_radius: Optional[float] + propellant_initial_mass: Optional[float] + throat_area: Optional[float] + throat_radius: Optional[float] + thrust_source: Optional[List[List[float]]] + total_impulse: Optional[float] + Kn: Optional[Any] + I_11: Optional[Any] + I_12: Optional[Any] + I_13: Optional[Any] + I_22: Optional[Any] + I_23: Optional[Any] + I_33: Optional[Any] + burn_area: Optional[Any] + burn_rate: Optional[Any] + burn_time: Optional[Any] + center_of_mass: Optional[Any] + center_of_propellant_mass: Optional[Any] + exhaust_velocity: Optional[Any] + grain_height: Optional[Any] + grain_volume: Optional[Any] + grain_inner_radius: Optional[Any] + mass_flow_rate: Optional[Any] + propellant_I_11: Optional[Any] + propellant_I_12: Optional[Any] + propellant_I_13: Optional[Any] + propellant_I_22: Optional[Any] + propellant_I_23: Optional[Any] + propellant_I_33: Optional[Any] + clip_thrust: Optional[Any] + propellant_mass: Optional[Any] + reshape_thrust_curve: Optional[Any] + total_mass: Optional[Any] + total_mass_flow_rate: Optional[Any] + thrust: Optional[Any] class MotorCreated(BaseModel): @@ -35,5 +84,5 @@ class MotorDeleted(BaseModel): message: str = "Motor successfully deleted" -class MotorPickle(BaseModel): - jsonpickle_rocketpy_motor: str +class MotorView(Motor): + selected_motor_kind: MotorKinds diff --git a/lib/views/rocket.py b/lib/views/rocket.py index 2fbf191..3ceac24 100644 --- a/lib/views/rocket.py +++ b/lib/views/rocket.py @@ -1,50 +1,39 @@ -from typing import List, Any, Optional +from typing import Any, Optional from pydantic import BaseModel - - -class InertiaDetails(BaseModel): - rocket_mass_without_propellant: str - rocket_mass_with_propellant: str - rocket_inertia_with_motor_without_propellant: "List[str]" - - -class RocketGeometricalParameters(BaseModel): - rocket_maximum_radius: str - rocket_frontal_area: str - rocket_codm_nozzle_exit_distance: str - rocket_codm_center_of_propellant_mass: str - rocket_codm_loaded_center_of_mass: str - - -class RocketAerodynamicsQuantities(BaseModel): - aerodynamics_lift_coefficient_derivatives: "Any" - aerodynamics_center_of_pressure: "Any" - distance_cop_to_codm: str - initial_static_margin: str - final_static_margin: str - - -class ParachuteData(BaseModel): - parachute_details: "Any" - # parachute_ejection_signal_trigger: "Any" - parachute_ejection_system_refresh_rate: "Optional[Any]" - parachute_lag: "Any" - - -class RocketData(BaseModel): - inertia_details: InertiaDetails - rocket_geometrical_parameters: RocketGeometricalParameters - rocket_aerodynamics_quantities: RocketAerodynamicsQuantities - parachute_data: ParachuteData - - -class RocketPlots(BaseModel): - pass - - -class RocketSummary(BaseModel): - rocket_data: RocketData - # rocket_plots: RocketPlots +from lib.models.rocket import Rocket +from lib.views.motor import MotorView, MotorSummary + + +class RocketSummary(MotorSummary): + # TODO: if Any is Callable, jumps pydantic parsing, expects a dill binary object + area: Optional[float] + center_of_mass_without_motor: Optional[float] + motor_center_of_dry_mass_position: Optional[float] + motor_position: Optional[float] + nozzle_position: Optional[float] + nozzle_to_cdm: Optional[float] + cp_eccentricity_x: Optional[float] + cp_eccentricity_y: Optional[float] + thrust_eccentricity_x: Optional[float] + thrust_eccentricity_y: Optional[float] + I_11_without_motor: Optional[Any] + I_12_without_motor: Optional[Any] + I_13_without_motor: Optional[Any] + I_22_without_motor: Optional[Any] + I_23_without_motor: Optional[Any] + I_33_without_motor: Optional[Any] + check_parachute_trigger: Optional[Any] + com_to_cdm_function: Optional[Any] + cp_position: Optional[Any] + motor_center_of_mass_position: Optional[Any] + nozzle_gyration_tensor: Optional[Any] + power_off_drag: Optional[Any] + power_on_drag: Optional[Any] + reduced_mass: Optional[Any] + stability_margin: Optional[Any] + static_margin: Optional[Any] + thrust_to_weight: Optional[Any] + total_lift_coeff_der: Optional[Any] class RocketCreated(BaseModel): @@ -62,5 +51,5 @@ class RocketDeleted(BaseModel): message: str = "Rocket successfully deleted" -class RocketPickle(BaseModel): - jsonpickle_rocketpy_rocket: str +class RocketView(Rocket): + motor: MotorView diff --git a/pyproject.toml b/pyproject.toml index 532a6f3..1d58e9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,23 +39,16 @@ skip-string-normalization = true [tool.pylint] max-line-length = 79 disable = """ - line-too-long, - duplicate-code, - use-dict-literal, missing-module-docstring, missing-function-docstring, missing-class-docstring, too-few-public-methods, too-many-public-methods, - too-many-instance-attributes, + line-too-long, + duplicate-code, logging-fstring-interpolation, - import-error, - protected-access, - unnecessary-dunder-call, - wrong-import-position, - consider-using-f-string, - too-many-function-args, - no-member, attribute-defined-outside-init, - no-else-raise + broad-exception-caught, + raise-missing-from, + too-many-instance-attributes, """ diff --git a/requirements.txt b/requirements.txt index ff629f3..0366b13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ motor +dill python-dotenv fastapi -pydantic +pydantic==1.10.18 pymongo jsonpickle gunicorn From 470eb1ea0bd5e4aaf17a42e94b82820529fddd54 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 14 Sep 2024 21:03:28 -0300 Subject: [PATCH 4/9] adds makefile to project --- Makefile | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0a54fd --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +black: + black ./lib + +lint: flake8 pylint + +flake8: + flake8 --ignore E501,E402,F401,W503 ./lib + +pylint: + pylint --extension-pkg-whitelist='pydantic' ./lib/* From eace9ee9f611845855f10db7c7b302d2de589fa8 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 14 Sep 2024 21:13:18 -0300 Subject: [PATCH 5/9] sets tip_chord as optional attr for Fins model; raises ValueError on unsupported fin kind --- lib/models/aerosurfaces.py | 3 ++- lib/services/rocket.py | 11 +---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/models/aerosurfaces.py b/lib/models/aerosurfaces.py index 14f0406..0d76565 100644 --- a/lib/models/aerosurfaces.py +++ b/lib/models/aerosurfaces.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Optional from pydantic import BaseModel @@ -28,7 +29,7 @@ class Fins(BaseModel): name: str n: int root_chord: float - tip_chord: float + tip_chord: Optional[float] span: float position: float cant_angle: float diff --git a/lib/services/rocket.py b/lib/services/rocket.py index a9e38fa..2b5842f 100644 --- a/lib/services/rocket.py +++ b/lib/services/rocket.py @@ -176,16 +176,7 @@ def get_rocketpy_finset(fins: Fins, kind: str) -> RocketPyFins: airfoil=fins.airfoil, ) case _: - rocketpy_finset = RocketPyTrapezoidalFins( - n=fins.n, - name=fins.name, - tip_chord=fins.tip_chord, - root_chord=fins.root_chord, - span=fins.span, - cant_angle=fins.cant_angle, - rocket_radius=fins.radius, - airfoil=fins.airfoil, - ) + raise ValueError(f"Invalid fins kind: {kind}") rocketpy_finset.position = fins.position return rocketpy_finset From 749a4ea0d2d8342900e6fd114204526fe1d786f4 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 14 Sep 2024 22:19:16 -0300 Subject: [PATCH 6/9] extends flight init parameters --- Makefile | 11 +++++++++++ lib/models/flight.py | 24 ++++++++++++++++++++++-- lib/services/flight.py | 10 ++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a0a54fd..0308d77 100644 --- a/Makefile +++ b/Makefile @@ -8,3 +8,14 @@ flake8: pylint: pylint --extension-pkg-whitelist='pydantic' ./lib/* + +dev: + python3 -m uvicorn lib:app --reload --port 3000 + +clean: + docker stop infinity-api + docker rm infinity-api + docker system prune -fa + +build: + docker build -t infinity-api . --no-cache diff --git a/lib/models/flight.py b/lib/models/flight.py index 0930a4f..82e01aa 100644 --- a/lib/models/flight.py +++ b/lib/models/flight.py @@ -1,11 +1,31 @@ +from enum import Enum +from typing import Optional from pydantic import BaseModel from lib.models.rocket import Rocket from lib.models.environment import Env +class EquationsOfMotion(str, Enum): + STANDARD = "STANDARD" + SOLID_PROPULSION = "SOLID_PROPULSION" + + class Flight(BaseModel): + name: str = "Flight" environment: Env = Env() rocket: Rocket = Rocket() - inclination: int = 85 - heading: int = 0 rail_length: float = 5.2 + inclination: Optional[int] = 80.0 + heading: Optional[int] = 90.0 + # TODO: implement initial_solution + terminate_on_apogee: Optional[bool] = False + max_time: Optional[int] = 600 + max_time_step: Optional[float] = 9999 + min_time_step: Optional[int] = 0 + rtol: Optional[float] = 1e-3 + atol: Optional[float] = 1e-3 + time_overshoot: Optional[bool] = True + verbose: Optional[bool] = False + equations_of_motion: Optional[EquationsOfMotion] = ( + EquationsOfMotion.STANDARD + ) diff --git a/lib/services/flight.py b/lib/services/flight.py index cdb4abe..8dba4df 100644 --- a/lib/services/flight.py +++ b/lib/services/flight.py @@ -35,6 +35,16 @@ def from_flight_model(cls, flight: Flight) -> Self: heading=flight.heading, environment=rocketpy_env, rail_length=flight.rail_length, + # initial_solution=flight.initial_solution, + terminate_on_apogee=flight.terminate_on_apogee, + max_time=flight.max_time, + max_time_step=flight.max_time_step, + min_time_step=flight.min_time_step, + rtol=flight.rtol, + atol=flight.atol, + time_overshoot=flight.time_overshoot, + verbose=flight.verbose, + equations_of_motion=flight.equations_of_motion.value.lower(), ) return cls(flight=rocketpy_flight) From 0f0cbd5f168ad455a8255244a73ed6f43a08c87f Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 14 Sep 2024 23:17:09 -0300 Subject: [PATCH 7/9] removes unnecessary attributes from views --- lib/views/flight.py | 1 - lib/views/motor.py | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/views/flight.py b/lib/views/flight.py index 1c4dd3c..1b3e87d 100644 --- a/lib/views/flight.py +++ b/lib/views/flight.py @@ -21,7 +21,6 @@ class FlightSummary(RocketSummary, EnvSummary): out_of_rail_time: Optional[float] out_of_rail_time_index: Optional[int] parachute_cd_s: Optional[float] - post_processed: Optional[bool] rail_length: Optional[float] rtol: Optional[float] t: Optional[float] diff --git a/lib/views/motor.py b/lib/views/motor.py index 9acd881..d1e6e8a 100644 --- a/lib/views/motor.py +++ b/lib/views/motor.py @@ -61,7 +61,6 @@ class MotorSummary(BaseModel): propellant_I_22: Optional[Any] propellant_I_23: Optional[Any] propellant_I_33: Optional[Any] - clip_thrust: Optional[Any] propellant_mass: Optional[Any] reshape_thrust_curve: Optional[Any] total_mass: Optional[Any] From e323123c9e5faa5989f38917e8c3752f90a54b4e Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sat, 14 Sep 2024 23:49:00 -0300 Subject: [PATCH 8/9] extends parachute input types --- lib/models/rocket.py | 4 ++-- lib/services/rocket.py | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/models/rocket.py b/lib/models/rocket.py index 401826e..54ed105 100644 --- a/lib/models/rocket.py +++ b/lib/models/rocket.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional, Tuple, List +from typing import Optional, Tuple, List, Union from pydantic import BaseModel from lib.models.motor import Motor from lib.models.aerosurfaces import ( @@ -21,7 +21,7 @@ class Parachute(BaseModel): cd_s: float = 10 sampling_rate: int = 105 lag: float = 1.5 - trigger: str = "lambda p, h, y: y[5] < 0 and h < 800" + trigger: Union[str, float] = "lambda p, h, y: y[5] < 0 and h < 800" noise: Tuple[float, float, float] = (0, 8.3, 0.5) diff --git a/lib/services/rocket.py b/lib/services/rocket.py index 2b5842f..c5d6554 100644 --- a/lib/services/rocket.py +++ b/lib/services/rocket.py @@ -21,6 +21,10 @@ from lib.views.rocket import RocketSummary +class InvalidParachuteTrigger(Exception): + """Exception raised for invalid parachute trigger expressions.""" + + class RocketService: _rocket: RocketPyRocket @@ -85,7 +89,9 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: trigger_expression := parachute.trigger ): parachute.trigger = eval( # pylint: disable=eval-used - trigger_expression, {"__builtins__": None}, {} + trigger_expression, + {"__builtins__": None}, + {"apogee": "apogee"}, ) rocketpy_parachute = cls.get_rocketpy_parachute(parachute) rocketpy_rocket.parachutes.append(rocketpy_parachute) @@ -217,7 +223,7 @@ def get_rocketpy_parachute(parachute: Parachute) -> RocketPyParachute: return rocketpy_parachute @staticmethod - def check_parachute_trigger(expression: str) -> bool: + def check_parachute_trigger(expression) -> bool: """ Check if the trigger expression is valid. @@ -228,9 +234,6 @@ def check_parachute_trigger(expression: str) -> bool: bool: True if the expression is valid, False otherwise. """ - class InvalidParachuteTrigger(Exception): - pass - # Parsing the expression into an AST try: parsed_expression = ast.parse(expression, mode="eval") From 07571f77edb42bab43b928a3a0d0a39a0a4b139e Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sun, 15 Sep 2024 00:13:08 -0300 Subject: [PATCH 9/9] minor code style change --- lib/services/rocket.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/services/rocket.py b/lib/services/rocket.py index c5d6554..572d7a2 100644 --- a/lib/services/rocket.py +++ b/lib/services/rocket.py @@ -45,12 +45,8 @@ def from_rocket_model(cls, rocket: Rocket) -> Self: radius=rocket.radius, mass=rocket.mass, inertia=rocket.inertia, - power_off_drag=( - rocket.power_off_drag if rocket.power_off_drag else None - ), - power_on_drag=( - rocket.power_on_drag if rocket.power_on_drag else None - ), + power_off_drag=(None or rocket.power_off_drag), + power_on_drag=(None or rocket.power_on_drag), center_of_mass_without_motor=rocket.center_of_mass_without_motor, coordinate_system_orientation=rocket.coordinate_system_orientation.value.lower(), )