Skip to content

Commit f526c33

Browse files
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
1 parent 4c99de6 commit f526c33

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1159
-1229
lines changed

.github/workflows/pylint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
strategy:
99
matrix:
10-
python-version: ["3.11.5"]
10+
python-version: ["3.12.5"]
1111
steps:
1212
- uses: actions/checkout@v3
1313
- name: Set up Python ${{ matrix.python-version }}

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11-slim-bookworm
1+
FROM python:3.12.5-slim-bookworm
22

33
EXPOSE 3000
44

@@ -16,4 +16,4 @@ RUN apt-get update && \
1616

1717
COPY ./lib /app/lib
1818

19-
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"]
19+
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"]

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
- [install mongodb-atlas](https://www.mongodb.com/try/download/community)
1010
- Install dependencies `python3 -m pip install -r requirements.txt`
1111

12+
## Development
13+
- black ./lib
14+
- pylint --extension-pkg-whitelist='pydantic' ./lib/*
15+
- flake8 --ignore E501,E402,F401,W503 ./lib
16+
1217
## Starting the server
1318
- Setup MONGODB_CONNECTION_STRING:
1419
```
@@ -62,7 +67,6 @@ $ touch .env && echo MONGODB_CONNECTION_STRING="$ConnectionString" > .env
6267
│   │   ├── environment.py
6368
│   │   ├── flight.py
6469
│   │   ├── motor.py
65-
│   │   ├── parachute.py
6670
│   │   └── rocket.py
6771
│   │  
6872
│   └── views
@@ -163,7 +167,7 @@ sequenceDiagram
163167
participant MongoDB
164168
participant Rocketpy lib
165169
166-
User ->> API: POST /simulate/rocketpy-model/:id
170+
User ->> API: POST /summary/rocketpy-model/:id
167171
API -->> MongoDB: Retrieve Rocketpy Model
168172
MongoDB -->> API: Rocketpy Model
169173
API ->> Rocketpy lib: Simulate Rocketpy Model

lib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ def parse_error(error):
2525
return f"{exc_type}: {exc_obj}"
2626

2727

28-
from lib.api import app
28+
from lib.api import app # pylint: disable=wrong-import-position,cyclic-import

lib/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
from lib.api import app
33

44
if __name__ == '__main__':
5-
app.run()
5+
app.run() # pylint: disable=no-member

lib/api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from fastapi import FastAPI, Request, status
66
from fastapi.exceptions import RequestValidationError
77
from fastapi.middleware.cors import CORSMiddleware
8-
from fastapi.middleware.gzip import GZipMiddleware
98
from fastapi.openapi.utils import get_openapi
109
from fastapi.responses import RedirectResponse, JSONResponse
1110

@@ -14,13 +13,15 @@
1413

1514
from lib import logger, parse_error
1615
from lib.routes import flight, environment, motor, rocket
16+
from lib.utils import RocketPyGZipMiddleware
1717

1818
app = FastAPI(
1919
swagger_ui_parameters={
2020
"defaultModelsExpandDepth": 0,
2121
"syntaxHighlight.theme": "obsidian",
2222
}
2323
)
24+
2425
app.add_middleware(
2526
CORSMiddleware,
2627
allow_origins=["*"],
@@ -37,15 +38,15 @@
3738
RequestsInstrumentor().instrument()
3839

3940
# Compress responses above 1KB
40-
app.add_middleware(GZipMiddleware, minimum_size=1000)
41+
app.add_middleware(RocketPyGZipMiddleware, minimum_size=1000)
4142

4243

4344
def custom_openapi():
4445
if app.openapi_schema:
4546
return app.openapi_schema
4647
openapi_schema = get_openapi(
4748
title="RocketPy Infinity-API",
48-
version="1.2.2 BETA",
49+
version="2.0.0",
4950
description=(
5051
"<p style='font-size: 18px;'>RocketPy Infinity-API is a RESTful Open API for RocketPy, a rocket flight simulator.</p>"
5152
"<br/>"
@@ -57,7 +58,6 @@ def custom_openapi():
5758
"<a href='https://api.rocketpy.org/redoc' style='color: white; text-decoration: none;'>ReDoc</a>"
5859
"</button>"
5960
"<p>Create, manage, and simulate rocket flights, environments, rockets, and motors.</p>"
60-
"<p>Currently, the API only supports TrapezoidalFins. We apologize for the limitation, but we are actively working to expand its capabilities soon.</p>"
6161
"<p>Please report any bugs at <a href='https://github.com/RocketPy-Team/infinity-api/issues/new/choose' style='text-decoration: none; color: #008CBA;'>GitHub Issues</a></p>"
6262
),
6363
routes=app.routes,

lib/controllers/environment.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Union
22

3-
import jsonpickle
43
from fastapi import HTTPException, status
54
from pymongo.errors import PyMongoError
65

@@ -13,7 +12,6 @@
1312
EnvCreated,
1413
EnvDeleted,
1514
EnvUpdated,
16-
EnvPickle,
1715
)
1816

1917

@@ -91,7 +89,7 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]:
9189
try:
9290
async with EnvRepository() as env_repo:
9391
await env_repo.get_env_by_id(env_id)
94-
read_env = env_repo.env
92+
env = env_repo.env
9593
except PyMongoError as e:
9694
logger.error(
9795
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]:
110108
detail=f"Failed to read environment: {exc_str}",
111109
) from e
112110
else:
113-
if read_env:
114-
return read_env
111+
if env:
112+
return env
115113
raise HTTPException(
116114
status_code=status.HTTP_404_NOT_FOUND,
117115
detail="Environment not found",
@@ -122,43 +120,41 @@ async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]:
122120
)
123121

124122
@classmethod
125-
async def get_rocketpy_env_as_jsonpickle(
123+
async def get_rocketpy_env_binary(
126124
cls,
127125
env_id: str,
128-
) -> Union[EnvPickle, HTTPException]:
126+
) -> Union[bytes, HTTPException]:
129127
"""
130-
Get rocketpy.Environmnet as jsonpickle string.
128+
Get rocketpy.Environmnet dill binary.
131129
132130
Args:
133131
env_id: str
134132
135133
Returns:
136-
views.EnvPickle
134+
bytes
137135
138136
Raises:
139137
HTTP 404 Not Found: If the env is not found in the database.
140138
"""
141139
try:
142-
read_env = await cls.get_env_by_id(env_id)
143-
rocketpy_env = EnvironmentService.from_env_model(read_env)
140+
env = await cls.get_env_by_id(env_id)
141+
env_service = EnvironmentService.from_env_model(env)
144142
except HTTPException as e:
145143
raise e from e
146144
except Exception as e:
147145
exc_str = parse_error(e)
148146
logger.error(
149-
f"controllers.environment.get_rocketpy_env_as_jsonpickle: {exc_str}"
147+
f"controllers.environment.get_rocketpy_env_as_binary: {exc_str}"
150148
)
151149
raise HTTPException(
152150
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
153151
detail=f"Failed to read environment: {exc_str}",
154152
) from e
155153
else:
156-
return EnvPickle(
157-
jsonpickle_rocketpy_env=jsonpickle.encode(rocketpy_env)
158-
)
154+
return env_service.get_env_binary()
159155
finally:
160156
logger.info(
161-
f"Call to controllers.environment.get_rocketpy_env_as_jsonpickle completed for Env {env_id}"
157+
f"Call to controllers.environment.get_rocketpy_env_binary completed for Env {env_id}"
162158
)
163159

164160
async def update_env_by_id(
@@ -263,9 +259,9 @@ async def simulate_env(
263259
HTTP 404 Not Found: If the env does not exist in the database.
264260
"""
265261
try:
266-
read_env = await cls.get_env_by_id(env_id)
267-
rocketpy_env = EnvironmentService.from_env_model(read_env)
268-
env_summary = rocketpy_env.get_env_summary()
262+
env = await cls.get_env_by_id(env_id)
263+
env_service = EnvironmentService.from_env_model(env)
264+
env_summary = env_service.get_env_summary()
269265
except HTTPException as e:
270266
raise e from e
271267
except Exception as e:

lib/controllers/flight.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
from pymongo.errors import PyMongoError
44

55

6-
import jsonpickle
7-
86
from lib import logger, parse_error
7+
from lib.controllers.rocket import RocketController
98
from lib.models.environment import Env
109
from lib.models.rocket import Rocket
1110
from lib.models.flight import Flight
11+
from lib.views.motor import MotorView
12+
from lib.views.rocket import RocketView
1213
from lib.views.flight import (
1314
FlightSummary,
1415
FlightCreated,
1516
FlightUpdated,
1617
FlightDeleted,
17-
FlightPickle,
18+
FlightView,
1819
)
1920
from lib.repositories.flight import FlightRepository
2021
from lib.services.flight import FlightService
@@ -42,6 +43,7 @@ def __init__(
4243
self,
4344
flight: Flight,
4445
):
46+
self.guard(flight)
4547
self._flight = flight
4648

4749
@property
@@ -52,6 +54,10 @@ def flight(self) -> Flight:
5254
def flight(self, flight: Flight):
5355
self._flight = flight
5456

57+
@staticmethod
58+
def guard(flight: Flight):
59+
RocketController.guard(flight.rocket)
60+
5561
async def create_flight(self) -> Union[FlightCreated, HTTPException]:
5662
"""
5763
Create a flight in the database.
@@ -85,7 +91,9 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]:
8591
)
8692

8793
@staticmethod
88-
async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]:
94+
async def get_flight_by_id(
95+
flight_id: str,
96+
) -> Union[FlightView, HTTPException]:
8997
"""
9098
Get a flight from the database.
9199
@@ -101,7 +109,7 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]:
101109
try:
102110
async with FlightRepository() as flight_repo:
103111
await flight_repo.get_flight_by_id(flight_id)
104-
read_flight = flight_repo.flight
112+
flight = flight_repo.flight
105113
except PyMongoError as e:
106114
logger.error(
107115
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]:
120128
detail=f"Failed to read flight: {exc_str}",
121129
) from e
122130
else:
123-
if read_flight:
124-
return read_flight
131+
if flight:
132+
motor_view = MotorView(
133+
**flight.rocket.motor.dict(),
134+
selected_motor_kind=flight.rocket.motor.motor_kind,
135+
)
136+
updated_rocket = flight.rocket.dict()
137+
updated_rocket.update(motor=motor_view)
138+
rocket_view = RocketView(
139+
**updated_rocket,
140+
)
141+
updated_flight = flight.dict()
142+
updated_flight.update(rocket=rocket_view)
143+
flight_view = FlightView(**updated_flight)
144+
return flight_view
125145
raise HTTPException(
126146
status_code=status.HTTP_404_NOT_FOUND,
127147
detail="Flight not found.",
@@ -132,43 +152,41 @@ async def get_flight_by_id(flight_id: str) -> Union[Flight, HTTPException]:
132152
)
133153

134154
@classmethod
135-
async def get_rocketpy_flight_as_jsonpickle(
155+
async def get_rocketpy_flight_binary(
136156
cls,
137157
flight_id: str,
138-
) -> Union[FlightPickle, HTTPException]:
158+
) -> Union[bytes, HTTPException]:
139159
"""
140-
Get rocketpy.flight as jsonpickle string.
160+
Get rocketpy.flight as dill binary.
141161
142162
Args:
143163
flight_id: str
144164
145165
Returns:
146-
views.FlightPickle
166+
bytes
147167
148168
Raises:
149169
HTTP 404 Not Found: If the flight is not found in the database.
150170
"""
151171
try:
152-
read_flight = await cls.get_flight_by_id(flight_id)
153-
rocketpy_flight = FlightService.from_flight_model(read_flight)
172+
flight = await cls.get_flight_by_id(flight_id)
173+
flight_service = FlightService.from_flight_model(flight)
154174
except HTTPException as e:
155175
raise e from e
156176
except Exception as e:
157177
exc_str = parse_error(e)
158178
logger.error(
159-
f"controllers.flight.get_rocketpy_flight_as_jsonpickle: {exc_str}"
179+
f"controllers.flight.get_rocketpy_flight_binary: {exc_str}"
160180
)
161181
raise HTTPException(
162182
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
163183
detail=f"Failed to read flight: {exc_str}",
164184
) from e
165185
else:
166-
return FlightPickle(
167-
jsonpickle_rocketpy_flight=jsonpickle.encode(rocketpy_flight)
168-
)
186+
return flight_service.get_flight_binary()
169187
finally:
170188
logger.info(
171-
f"Call to controllers.flight.get_rocketpy_flight_as_jsonpickle completed for Flight {flight_id}"
189+
f"Call to controllers.flight.get_rocketpy_flight_binary completed for Flight {flight_id}"
172190
)
173191

174192
async def update_flight_by_id(
@@ -229,11 +247,9 @@ async def update_env_by_flight_id(
229247
HTTP 404 Not Found: If the flight is not found in the database.
230248
"""
231249
try:
232-
read_flight = await cls.get_flight_by_id(flight_id)
233-
new_flight = read_flight.dict()
234-
new_flight["environment"] = env
235-
new_flight = Flight(**new_flight)
236-
async with FlightRepository(new_flight) as flight_repo:
250+
flight = await cls.get_flight_by_id(flight_id)
251+
flight.environment = env
252+
async with FlightRepository(flight) as flight_repo:
237253
await flight_repo.update_env_by_flight_id(flight_id)
238254
except PyMongoError as e:
239255
logger.error(
@@ -277,16 +293,9 @@ async def update_rocket_by_flight_id(
277293
HTTP 404 Not Found: If the flight is not found in the database.
278294
"""
279295
try:
280-
read_flight = await cls.get_flight_by_id(flight_id)
281-
updated_rocket = rocket.dict()
282-
updated_rocket["rocket_option"] = rocket.rocket_option.value
283-
updated_rocket["motor"][
284-
"motor_kind"
285-
] = rocket.motor.motor_kind.value
286-
new_flight = read_flight.dict()
287-
new_flight["rocket"] = updated_rocket
288-
new_flight = Flight(**new_flight)
289-
async with FlightRepository(new_flight) as flight_repo:
296+
flight = await cls.get_flight_by_id(flight_id)
297+
flight.rocket = rocket
298+
async with FlightRepository(flight) as flight_repo:
290299
await flight_repo.update_rocket_by_flight_id(flight_id)
291300
except PyMongoError as e:
292301
logger.error(
@@ -371,9 +380,9 @@ async def simulate_flight(
371380
HTTP 404 Not Found: If the flight does not exist in the database.
372381
"""
373382
try:
374-
read_flight = await cls.get_flight_by_id(flight_id=flight_id)
375-
rocketpy_flight = FlightService.from_flight_model(read_flight)
376-
flight_summary = rocketpy_flight.get_flight_summary()
383+
flight = await cls.get_flight_by_id(flight_id=flight_id)
384+
flight_service = FlightService.from_flight_model(flight)
385+
flight_summary = flight_service.get_flight_summary()
377386
except HTTPException as e:
378387
raise e from e
379388
except Exception as e:

0 commit comments

Comments
 (0)