22Flight routes with dependency injection for improved performance.
33"""
44
5- from fastapi import APIRouter , Response
5+ import json
6+
7+ from fastapi import APIRouter , Response , UploadFile , File
68from opentelemetry import trace
79
810from src .views .flight import (
911 FlightSimulation ,
1012 FlightCreated ,
1113 FlightRetrieved ,
14+ FlightImported ,
1215)
1316from src .models .environment import EnvironmentModel
1417from src .models .flight import FlightModel , FlightWithReferencesRequest
@@ -76,6 +79,7 @@ async def read_flight(
7679 with tracer .start_as_current_span ("read_flight" ):
7780 return await controller .get_flight_by_id (flight_id )
7881
82+
7983@router .put ("/{flight_id}" , status_code = 204 )
8084async def update_flight (
8185 flight_id : str ,
@@ -117,6 +121,7 @@ async def update_flight_from_references(
117121 flight_id , payload
118122 )
119123
124+
120125@router .delete ("/{flight_id}" , status_code = 204 )
121126async def delete_flight (
122127 flight_id : str ,
@@ -136,34 +141,104 @@ async def delete_flight(
136141 "/{flight_id}/rocketpy" ,
137142 responses = {
138143 200 : {
139- "description" : "Binary file download" ,
140- "content" : {"application/octet-stream " : {}},
144+ "description" : "Portable .rpy JSON file download" ,
145+ "content" : {"application/json " : {}},
141146 }
142147 },
143148 status_code = 200 ,
144149 response_class = Response ,
145150)
151+ async def get_rocketpy_flight_rpy (
152+ flight_id : str ,
153+ controller : FlightControllerDep ,
154+ ):
155+ """
156+ Export a rocketpy Flight as a portable ``.rpy`` JSON file.
157+
158+ The ``.rpy`` format is architecture-, OS-, and
159+ Python-version-agnostic.
160+
161+ ## Args
162+ ``` flight_id: str ```
163+ """
164+ with tracer .start_as_current_span ("get_rocketpy_flight_rpy" ):
165+ headers = {
166+ 'Content-Disposition' : (
167+ 'attachment; filename=' f'"rocketpy_flight_{ flight_id } .rpy"'
168+ ),
169+ }
170+ rpy = await controller .get_rocketpy_flight_rpy (flight_id )
171+ return Response (
172+ content = rpy ,
173+ headers = headers ,
174+ media_type = "application/json" ,
175+ status_code = 200 ,
176+ )
146177
147- async def get_rocketpy_flight_binary (
178+
179+ @router .post (
180+ "/upload" ,
181+ status_code = 201 ,
182+ responses = {
183+ 201 : {"description" : "Flight imported from .rpy file" },
184+ 422 : {"description" : "Invalid .rpy file" },
185+ },
186+ )
187+ async def import_flight_from_rpy (
188+ file : UploadFile = File (...),
189+ controller : FlightControllerDep = None ,
190+ ) -> FlightImported :
191+ """
192+ Upload a ``.rpy`` JSON file containing a RocketPy Flight.
193+
194+ The file is deserialized and decomposed into its
195+ constituent objects (Environment, Motor, Rocket, Flight).
196+ Each object is persisted as a normal JSON model and the
197+ corresponding IDs are returned.
198+
199+ ## Args
200+ ``` file: .rpy JSON upload ```
201+ """
202+ with tracer .start_as_current_span ("import_flight_from_rpy" ):
203+ content = await file .read ()
204+ return await controller .import_flight_from_rpy (content )
205+
206+
207+ @router .get (
208+ "/{flight_id}/notebook" ,
209+ responses = {
210+ 200 : {
211+ "description" : "Jupyter notebook file download" ,
212+ "content" : {"application/x-ipynb+json" : {}},
213+ }
214+ },
215+ status_code = 200 ,
216+ response_class = Response ,
217+ )
218+ async def get_flight_notebook (
148219 flight_id : str ,
149220 controller : FlightControllerDep ,
150221):
151222 """
152- Loads rocketpy.flight as a dill binary.
153- Currently only amd64 architecture is supported.
223+ Export a flight as a Jupyter notebook (.ipynb).
224+
225+ The notebook loads the flight's ``.rpy`` file and calls
226+ ``flight.all_info()`` for interactive exploration.
154227
155228 ## Args
156229 ``` flight_id: str ```
157230 """
158- with tracer .start_as_current_span ("get_rocketpy_flight_binary" ):
231+ with tracer .start_as_current_span ("get_flight_notebook" ):
232+ notebook = await controller .get_flight_notebook (flight_id )
233+ content = json .dumps (notebook , indent = 1 ).encode ()
234+ filename = f"flight_{ flight_id } .ipynb"
159235 headers = {
160- ' Content-Disposition' : f'attachment; filename="rocketpy_flight_ { flight_id } .dill"'
236+ " Content-Disposition" : ( f'attachment; filename="{ filename } "' ),
161237 }
162- binary = await controller .get_rocketpy_flight_binary (flight_id )
163238 return Response (
164- content = binary ,
239+ content = content ,
165240 headers = headers ,
166- media_type = "application/octet-stream " ,
241+ media_type = "application/x-ipynb+json " ,
167242 status_code = 200 ,
168243 )
169244
@@ -210,6 +285,7 @@ async def update_flight_rocket(
210285 rocket = rocket ,
211286 )
212287
288+
213289@router .get ("/{flight_id}/simulate" )
214290async def get_flight_simulation (
215291 flight_id : str ,
@@ -222,4 +298,4 @@ async def get_flight_simulation(
222298 ``` flight_id: Flight ID ```
223299 """
224300 with tracer .start_as_current_span ("get_flight_simulation" ):
225- return await controller .get_flight_simulation (flight_id )
301+ return await controller .get_flight_simulation (flight_id )
0 commit comments