Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions src/controllers/flight.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from fastapi import HTTPException, status

from src.controllers.interface import (
ControllerBase,
controller_exception_handler,
)
from src.views.flight import FlightSimulation
from src.models.flight import FlightModel
from src.views.flight import FlightSimulation, FlightCreated
from src.models.flight import (
FlightModel,
FlightWithReferencesRequest,
)
from src.models.environment import EnvironmentModel
from src.models.rocket import RocketModel
from src.repositories.interface import RepositoryInterface
from src.services.flight import FlightService


Expand All @@ -21,6 +27,56 @@ class FlightController(ControllerBase):
def __init__(self):
super().__init__(models=[FlightModel])

async def _load_environment(self, environment_id: str) -> EnvironmentModel:
repo_cls = RepositoryInterface.get_model_repo(EnvironmentModel)
async with repo_cls() as repo:
environment = await repo.read_environment_by_id(environment_id)
if environment is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Environment not found",
)
return environment

async def _load_rocket(self, rocket_id: str) -> RocketModel:
repo_cls = RepositoryInterface.get_model_repo(RocketModel)
async with repo_cls() as repo:
rocket = await repo.read_rocket_by_id(rocket_id)
if rocket is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Rocket not found",
)
return rocket

@controller_exception_handler
async def create_flight_from_references(
self, payload: FlightWithReferencesRequest
) -> FlightCreated:
environment = await self._load_environment(payload.environment_id)
rocket = await self._load_rocket(payload.rocket_id)
flight_model = payload.flight.assemble(
environment=environment,
rocket=rocket,
)
return await self.post_flight(flight_model)

@controller_exception_handler
async def update_flight_from_references(
self,
flight_id: str,
payload: FlightWithReferencesRequest,
) -> None:
environment = await self._load_environment(payload.environment_id)
rocket = await self._load_rocket(payload.rocket_id)
flight_model = payload.flight.assemble(
environment=environment,
rocket=rocket,
)
flight_model.set_id(flight_id)
await self.put_flight_by_id(flight_id, flight_model)
return

@controller_exception_handler
async def update_environment_by_flight_id(
self, flight_id: str, *, environment: EnvironmentModel
Expand Down
42 changes: 40 additions & 2 deletions src/controllers/rocket.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from fastapi import HTTPException, status

from src.controllers.interface import (
ControllerBase,
controller_exception_handler,
)
from src.views.rocket import RocketSimulation
from src.models.rocket import RocketModel
from src.views.rocket import RocketSimulation, RocketCreated
from src.models.motor import MotorModel
from src.models.rocket import (
RocketModel,
RocketWithMotorReferenceRequest,
)
from src.repositories.interface import RepositoryInterface
from src.services.rocket import RocketService


Expand All @@ -19,6 +26,37 @@ class RocketController(ControllerBase):
def __init__(self):
super().__init__(models=[RocketModel])

async def _load_motor(self, motor_id: str) -> MotorModel:
repo_cls = RepositoryInterface.get_model_repo(MotorModel)
async with repo_cls() as repo:
motor = await repo.read_motor_by_id(motor_id)
if motor is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Motor not found",
)
return motor

@controller_exception_handler
async def create_rocket_from_motor_reference(
self, payload: RocketWithMotorReferenceRequest
) -> RocketCreated:
motor = await self._load_motor(payload.motor_id)
rocket_model = payload.rocket.assemble(motor)
return await self.post_rocket(rocket_model)

@controller_exception_handler
async def update_rocket_from_motor_reference(
self,
rocket_id: str,
payload: RocketWithMotorReferenceRequest,
) -> None:
motor = await self._load_motor(payload.motor_id)
rocket_model = payload.rocket.assemble(motor)
rocket_model.set_id(rocket_id)
await self.put_rocket_by_id(rocket_id, rocket_model)
return

@controller_exception_handler
async def get_rocketpy_rocket_binary(self, rocket_id: str) -> bytes:
"""
Expand Down
43 changes: 43 additions & 0 deletions src/models/flight.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional, Self, ClassVar, Literal

from pydantic import BaseModel, Field
from src.models.interface import ApiBaseModel
from src.models.rocket import RocketModel
from src.models.environment import EnvironmentModel
Expand Down Expand Up @@ -69,3 +71,44 @@ def RETRIEVED(model_instance: type(Self)):
**model_instance.model_dump(),
)
)


class FlightPartialModel(BaseModel):
"""Flight attributes required when rocket/environment are referenced."""

name: str = Field(default="flight")
rail_length: float = 1
time_overshoot: bool = True
terminate_on_apogee: bool = False
equations_of_motion: Literal['standard', 'solid_propulsion'] = 'standard'
inclination: float = 90.0
heading: float = 0.0
max_time: Optional[int] = None
max_time_step: Optional[float] = None
min_time_step: Optional[int] = None
rtol: Optional[float] = None
atol: Optional[float] = None
verbose: Optional[bool] = None

def assemble(
self,
*,
environment: EnvironmentModel,
rocket: RocketModel,
) -> FlightModel:
"""Compose a full flight model using referenced resources."""

flight_data = self.model_dump(exclude_none=True)
return FlightModel(
environment=environment,
rocket=rocket,
**flight_data,
)


class FlightWithReferencesRequest(BaseModel):
"""Payload for creating or updating flights via component references."""

environment_id: str
rocket_id: str
flight: FlightPartialModel
42 changes: 42 additions & 0 deletions src/models/rocket.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional, Tuple, List, Union, Self, ClassVar, Literal

from pydantic import BaseModel, Field
from src.models.interface import ApiBaseModel
from src.models.motor import MotorModel
from src.models.sub.aerosurfaces import (
Expand Down Expand Up @@ -61,3 +63,43 @@ def RETRIEVED(model_instance: type(Self)):
**model_instance.model_dump(),
)
)


class RocketPartialModel(BaseModel):
"""Rocket attributes required when a motor is supplied by reference."""

radius: float
mass: float
motor_position: float
center_of_mass_without_motor: float
inertia: Union[
Tuple[float, float, float],
Tuple[float, float, float, float, float, float],
] = (0, 0, 0)
power_off_drag: List[Tuple[float, float]] = Field(
default_factory=lambda: [(0, 0)]
)
power_on_drag: List[Tuple[float, float]] = Field(
default_factory=lambda: [(0, 0)]
)
coordinate_system_orientation: Literal['tail_to_nose', 'nose_to_tail'] = (
'tail_to_nose'
)
nose: NoseCone
fins: List[Fins]
parachutes: Optional[List[Parachute]] = None
rail_buttons: Optional[RailButtons] = None
tail: Optional[Tail] = None

def assemble(self, motor: MotorModel) -> RocketModel:
"""Compose a full rocket model using the referenced motor."""

rocket_data = self.model_dump(exclude_none=True)
return RocketModel(motor=motor, **rocket_data)


class RocketWithMotorReferenceRequest(BaseModel):
"""Payload for creating or updating rockets via motor reference."""

motor_id: str
rocket: RocketPartialModel
44 changes: 43 additions & 1 deletion src/routes/flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
FlightRetrieved,
)
from src.models.environment import EnvironmentModel
from src.models.flight import FlightModel
from src.models.flight import FlightModel, FlightWithReferencesRequest
from src.models.rocket import RocketModel
from src.controllers.flight import FlightController

Expand Down Expand Up @@ -41,6 +41,25 @@ async def create_flight(flight: FlightModel) -> FlightCreated:
return await controller.post_flight(flight)


@router.post("/from-references", status_code=201)
async def create_flight_from_references(
payload: FlightWithReferencesRequest,
) -> FlightCreated:
"""
Creates a flight using existing rocket and environment references.

## Args
```
environment_id: str
rocket_id: str
flight: Flight-only fields JSON
```
"""
with tracer.start_as_current_span("create_flight_from_references"):
controller = FlightController()
return await controller.create_flight_from_references(payload)


@router.get("/{flight_id}")
async def read_flight(flight_id: str) -> FlightRetrieved:
"""
Expand Down Expand Up @@ -70,6 +89,29 @@ async def update_flight(flight_id: str, flight: FlightModel) -> None:
return await controller.put_flight_by_id(flight_id, flight)


@router.put("/{flight_id}/from-references", status_code=204)
async def update_flight_from_references(
flight_id: str,
payload: FlightWithReferencesRequest,
) -> None:
"""
Updates a flight using existing rocket and environment references.

## Args
```
flight_id: str
environment_id: str
rocket_id: str
flight: Flight-only fields JSON
```
"""
with tracer.start_as_current_span("update_flight_from_references"):
controller = FlightController()
return await controller.update_flight_from_references(
flight_id, payload
)


@router.delete("/{flight_id}", status_code=204)
async def delete_flight(flight_id: str) -> None:
"""
Expand Down
45 changes: 44 additions & 1 deletion src/routes/rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
RocketCreated,
RocketRetrieved,
)
from src.models.rocket import RocketModel
from src.models.rocket import (
RocketModel,
RocketWithMotorReferenceRequest,
)
from src.controllers.rocket import RocketController

router = APIRouter(
Expand Down Expand Up @@ -39,6 +42,24 @@ async def create_rocket(rocket: RocketModel) -> RocketCreated:
return await controller.post_rocket(rocket)


@router.post("/from-motor-reference", status_code=201)
async def create_rocket_from_motor_reference(
payload: RocketWithMotorReferenceRequest,
) -> RocketCreated:
"""
Creates a rocket using an existing motor reference.

## Args
```
motor_id: str
rocket: Rocket-only fields JSON
```
"""
with tracer.start_as_current_span("create_rocket_from_motor_reference"):
controller = RocketController()
return await controller.create_rocket_from_motor_reference(payload)


@router.get("/{rocket_id}")
async def read_rocket(rocket_id: str) -> RocketRetrieved:
"""
Expand Down Expand Up @@ -68,6 +89,28 @@ async def update_rocket(rocket_id: str, rocket: RocketModel) -> None:
return await controller.put_rocket_by_id(rocket_id, rocket)


@router.put("/{rocket_id}/from-motor-reference", status_code=204)
async def update_rocket_from_motor_reference(
rocket_id: str,
payload: RocketWithMotorReferenceRequest,
) -> None:
"""
Updates a rocket using an existing motor reference.

## Args
```
rocket_id: str
motor_id: str
rocket: Rocket-only fields JSON
```
"""
with tracer.start_as_current_span("update_rocket_from_motor_reference"):
controller = RocketController()
return await controller.update_rocket_from_motor_reference(
rocket_id, payload
)


@router.delete("/{rocket_id}", status_code=204)
async def delete_rocket(rocket_id: str) -> None:
"""
Expand Down
Loading