Skip to content

API V3 Refactoring #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Mar 8, 2025
Merged

API V3 Refactoring #44

merged 34 commits into from
Mar 8, 2025

Conversation

GabrielBarberini
Copy link
Collaborator

@GabrielBarberini GabrielBarberini commented Feb 22, 2025

High level view

  • Abstract common features from controllers, repositories, models and views as interfaces;
  • Improve route path semantics;
  • Improve documentation.

Summary by CodeRabbit

  • New Features
    • Enhanced API endpoints for environment, flight, motor, and rocket operations including simulation and binary retrieval.
    • Introduced new controllers and repositories for managing environments, flights, motors, and rockets.
    • New data models for environmental, flight, motor, and rocket simulations.
    • New test suite for environment, flight, motor, and rocket routes with comprehensive coverage.
  • Refactor
    • Complete reorganization of the project structure with updated configurations and tooling for improved maintainability.
    • Updated import paths throughout the codebase to reflect the new directory structure.
  • Documentation
    • Revised setup instructions and usage guides to reflect new module paths and updated commands.
  • Tests
    • Overhauled and expanded test suites to ensure robust error handling and reliable performance across all endpoints, including new tests for the ControllerInterface and RepositoryInterface.
    • Added comprehensive unit tests for environment, flight, motor, and rocket routes.
    • Removed outdated test files for environment, flight, motor, and rocket routes.

Copy link
Contributor

coderabbitai bot commented Feb 22, 2025

Walkthrough

This pull request restructures the project by replacing the old “lib” directory with a new “src” directory. File paths for Docker, Makefile, README, and module imports were updated accordingly. Numerous obsolete controllers, models, repositories, routes, and views in the “lib” folder have been removed and reimplemented in “src”. New service, controller, model, and repository classes now provide unified CRUD and simulation operations, along with improved error handling and updated testing configurations.

Changes

File(s) Change Summary
Dockerfile, Makefile, README.md, src/__init__.py, src/__main__.py, src/api.py, src/settings/gunicorn.py Updated paths and module references from “lib” to “src”; changed commands and configuration details to reflect a new directory structure.
lib/controllers/*, lib/models/*, lib/repositories/*, lib/routes/*, lib/views/* Removed obsolete implementations of controllers, models, repositories, routes, and views for environment, flight, motor, and rocket.
src/controllers/*, src/models/*, src/repositories/*, src/routes/*, src/views/* Introduced new implementations providing CRUD and simulation functionalities with enhanced exception handling and consistent model interfaces.
src/services/*, src/utils.py Updated service methods and utility functions; renamed methods and adjusted type handling and error conditions.
pyproject.toml, requirements-dev.txt Added new pytest configuration, updated linting rules (pylint and ruff), and included new dependencies (pytest_asyncio, black).
tests/* Removed legacy tests for the “lib” routes; added new unit and integration tests for the “src” routes, controllers, and repository interfaces; updated fixtures to match the new model names and structure.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant Controller
    participant Service
    participant Repository

    Client->>Router: HTTP Request (e.g., Create Environment)
    Router->>Controller: Invoke Endpoint
    Controller->>Service: Process business logic & simulation
    Service->>Repository: Execute CRUD operation
    Repository-->>Service: Return data/result
    Service-->>Controller: Process and format result
    Controller-->>Router: Return response payload
    Router-->>Client: HTTP Response with status & data
Loading

Possibly related PRs

  • Major API refactoring #25: The changes in the main PR, which involve restructuring file paths and updating commands in the Dockerfile, Makefile, and README.md, are related to the modifications in the retrieved PR that also focus on restructuring and enhancing the organization of the project, particularly in the Makefile and model definitions. Both PRs reflect a significant shift in the project's directory structure and operational commands.

Suggested reviewers

  • phmbressan
  • aasitvora99
  • Gui-FernandesBR

Poem

I'm a coding rabbit, hopping through refactors galore,
Leaving old “lib” trails for “src” to explore.
Endpoints and controllers now prance in new light,
With services and models shining ever so bright.
I nibble on bugs and skip past code too tight —
In this garden of clean structure, everything feels just right!
🐇💻


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🔭 Outside diff range comments (1)
src/utils.py (1)

104-104: ⚠️ Potential issue

Fix unreachable code after return statements.

The code after the return statements at lines 104, 118, and 132 is unreachable.

Apply this diff to fix the unreachable code:

             await self.send(self.initial_message)
-            await self.send(message)
+            return await self.send(message)

             await self.send(self.initial_message)
-            await self.send(message)
+            return await self.send(message)

             await self.send(self.initial_message)
-            await self.send(message)
+            return await self.send(message)

Also applies to: 118-118, 132-132

🧰 Tools
🪛 GitHub Actions: Pylint

[warning] 104-104: W0101: Unreachable code (unreachable)

🧹 Nitpick comments (47)
pyproject.toml (1)

55-56: Review Pylint Disable Additions
The additions of no-member and protected-access in the [tool.pylint] disable list allow for more flexibility by suppressing errors related to member access and protected attributes. However, consider documenting the rationale behind disabling these rules (e.g., in a developer guidelines document) to ensure that their usage is intentional and to mitigate the risk of overlooking genuine issues.

src/models/interface.py (1)

25-34: Align the property naming with Python conventions
Properties NAME and METHODS deviate from typical snake_case naming. If the uppercase style is intentional for emphasis, add clarifying docstrings or keep them consistent across the codebase.

src/controllers/interface.py (3)

12-36: Ensure exception clarity
Catching Exception risks obscuring specific error cases. Logging covers the traceback, but consider raising or mapping them to more detailed custom HTTP errors for clarity.


45-57: Dynamic method assignment can reduce discoverability
Creating methods on the fly is flexible, though it may confuse static analysis and code browsing. Consider an explicit dictionary lookup to improve maintainability.


70-125: Consider partial updates as a separate method
Currently, only _put_model is provided for updates, which typically implies full resource replacement. If partial updates are needed, implementing a _patch_model method may avoid confusion.

src/services/rocket.py (1)

30-53: Support for multi-stage or multiple motors
from_rocket_model currently configures only one motor. If future requirements involve multiple motors or stages, consider incorporating a list or layered approach.

src/views/flight.py (4)

9-154: Consider splitting or grouping fields within FlightSimulation.
This class contains an extremely large number of fields, which can become unwieldy and difficult to maintain. Consider grouping related fields into sub-models or dedicated structures for better organization and clarity.


156-159: Add docstring for FlightView.
Including a short docstring will help other developers understand how this view differs from FlightModel and clarify its use (e.g., why a separate flight_id is needed in addition to inherited fields).


161-164: Provide additional context or docstring for FlightCreated.
Currently, this class only declares a message and a flight ID. A brief explanation of what the creation workflow entails will improve clarity for integrators.


166-168: Retain consistent naming in FlightRetrieved.
Following the naming convention of FlightCreated, consider whether you want to name it FlightRead or maintain a consistent pattern if there are other classes named similarly (e.g., FlightDeleted or FlightUpdated).

src/repositories/interface.py (3)

114-119: Avoid coupling models too tightly to the repository.
You're passing an instance of ApiBaseModel to __init__. Ensure that this design remains flexible enough if you ever need to handle multiple model variations or run migrations.


230-238: Return HTTP 404 for missing records.
Returning None when a record is not found might confuse clients. Raise a 404 or handle it similarly to ensure consistent API responses.


240-244: Check deletion results.
Similar to updates, delete_one does not confirm whether a record was actually deleted. Consider returning a boolean or raising a 404 error if no deletion occurred.

src/repositories/motor.py (3)

9-15: Include more descriptive class-level docs.
The docstring simply restates "CRUD operations." Consider explaining usage scenarios or constraints, such as concurrency or large data sets.


25-26: Return an explicit 404 or None on missing records.
read_motor_by_id may return None, so ensure it’s handled correctly upstream. If guaranteeing a 404 in the router is desired, raise an exception for consistency.


33-34: Check deletion results to identify non-existent records.
Similar to updates, verify successful deletion and treat nonexistent records appropriately (e.g., log, raise 404).

src/repositories/rocket.py (1)

10-15: Enhance class documentation.

Consider adding:

  • Return types for each method
  • Exception details for error handling
  • Example usage
src/repositories/flight.py (1)

1-34: Consider extracting common repository implementation.

The implementation is nearly identical to RocketRepository. Consider creating a base class that implements these common CRUD operations to reduce code duplication.

Example approach:

class BaseRepository(RepositoryInterface):
    def __init__(self, model_class):
        super().__init__(model_class)

    @repository_exception_handler
    async def create(self, model: BaseModel) -> str:
        return await self.insert(model.model_dump())

    # ... other common methods

class FlightRepository(BaseRepository):
    def __init__(self):
        super().__init__(FlightModel)
src/models/environment.py (1)

23-25: Consider using UTC for date default.

The default date calculation using datetime.datetime.today() could lead to inconsistent behavior across different timezones and during testing. Consider using datetime.datetime.utcnow() instead.

-    date: Optional[datetime.datetime] = (
-        datetime.datetime.today() + datetime.timedelta(days=1)
-    )
+    date: Optional[datetime.datetime] = (
+        datetime.datetime.utcnow() + datetime.timedelta(days=1)
+    )
src/models/sub/aerosurfaces.py (2)

31-44: Add validation for fins parameters.

Consider adding Field validators with descriptions and constraints for the fins parameters to improve API usability and prevent invalid inputs.

class Fins(BaseModel):
-    fins_kind: Literal['trapezoidal', 'elliptical']
+    fins_kind: Literal['trapezoidal', 'elliptical'] = Field(
+        ...,
+        description="Type of fins geometry"
+    )
-    n: int
+    n: int = Field(..., gt=0, description="Number of fins")
-    root_chord: float
+    root_chord: float = Field(..., gt=0, description="Length of the root chord")
-    span: float
+    span: float = Field(..., gt=0, description="Span length of the fins")

56-58: Track TODO comment for airbrakes implementation.

The TODO comment for implementing airbrakes should be tracked in the issue tracker.

Would you like me to create an issue to track the airbrakes implementation?

src/models/rocket.py (1)

27-28: Consider more meaningful default values for drag coefficients.

The current default values of [(0, 0)] for drag coefficients might not be the most meaningful defaults. Consider using more realistic values or requiring these parameters.

-    power_off_drag: List[Tuple[float, float]] = [(0, 0)]
-    power_on_drag: List[Tuple[float, float]] = [(0, 0)]
+    power_off_drag: List[Tuple[float, float]] = Field(
+        ...,
+        description="List of (Mach number, Drag coefficient) pairs for power-off configuration"
+    )
+    power_on_drag: List[Tuple[float, float]] = Field(
+        ...,
+        description="List of (Mach number, Drag coefficient) pairs for power-on configuration"
+    )
src/controllers/rocket.py (1)

22-40: Enhance error documentation for better clarity.

While the method is well-documented, consider adding specific error cases that could trigger the HTTP 404, such as invalid ID format or database connection issues.

src/services/environment.py (1)

37-43: Consider making the environment property private.

The property and its setter could be renamed to _environment to match the private variable naming convention, as it's an internal implementation detail.

-    @property
-    def environment(self) -> RocketPyEnvironment:
+    @property
+    def _environment(self) -> RocketPyEnvironment:
         return self._environment

-    @environment.setter
-    def environment(self, environment: RocketPyEnvironment):
+    @_environment.setter
+    def _environment(self, environment: RocketPyEnvironment):
         self._environment = environment
src/controllers/environment.py (1)

28-28: Fix typo in docstring.

There's a typo in "rocketpy.Environmnet".

-        Get rocketpy.Environmnet dill binary.
+        Get rocketpy.Environment dill binary.
src/models/flight.py (2)

22-22: Address the TODO comment for initial_solution implementation.

The TODO comment indicates missing functionality. Would you like me to help implement the initial_solution feature?


14-17: Consider extracting default values as class constants.

The default values for rail_length, time_overshoot, terminate_on_apogee, and equations_of_motion could be defined as class constants for better maintainability.

+    DEFAULT_RAIL_LENGTH: ClassVar[float] = 1.0
+    DEFAULT_TIME_OVERSHOOT: ClassVar[bool] = True
+    DEFAULT_TERMINATE_ON_APOGEE: ClassVar[bool] = True
+    DEFAULT_EQUATIONS_OF_MOTION: ClassVar[str] = 'standard'

-    rail_length: float = 1
-    time_overshoot: bool = True
-    terminate_on_apogee: bool = True
-    equations_of_motion: Literal['standard', 'solid_propulsion'] = 'standard'
+    rail_length: float = DEFAULT_RAIL_LENGTH
+    time_overshoot: bool = DEFAULT_TIME_OVERSHOOT
+    terminate_on_apogee: bool = DEFAULT_TERMINATE_ON_APOGEE
+    equations_of_motion: Literal['standard', 'solid_propulsion'] = DEFAULT_EQUATIONS_OF_MOTION
src/services/flight.py (1)

51-60: Consider adding docstring parameters and return type hints.

While the method renaming better reflects its functionality, the docstring could be more comprehensive.

 def get_flight_simulation(self) -> FlightSimulation:
     """
     Get the simulation of the flight.
+
+    Args:
+        None
+
+    Returns:
+        FlightSimulation: A simulation object containing flight attributes
     """
     attributes = get_instance_attributes(self.flight)
     flight_simulation = FlightSimulation(**attributes)
     return flight_simulation
src/views/rocket.py (1)

41-53: Consider adding validation for rocket_id format.

While the view classes are well-structured, adding validation for the rocket_id format would enhance data integrity.

 class RocketView(RocketModel):
-    rocket_id: Optional[str] = None
+    rocket_id: Optional[str] = Field(
+        None,
+        pattern="^[a-zA-Z0-9-_]+$",
+        description="Rocket ID in alphanumeric format"
+    )
     motor: MotorView
src/views/environment.py (1)

26-28: Consider using a single date field with computed properties.

Having three separate date fields with the same default value could lead to inconsistencies. Consider using a single source of truth.

-    date: Optional[datetime] = datetime.today() + timedelta(days=1)
-    local_date: Optional[datetime] = datetime.today() + timedelta(days=1)
-    datetime_date: Optional[datetime] = datetime.today() + timedelta(days=1)
+    date: Optional[datetime] = datetime.today() + timedelta(days=1)
+    
+    @property
+    def local_date(self) -> datetime:
+        return self.date
+        
+    @property
+    def datetime_date(self) -> datetime:
+        return self.date
src/models/motor.py (1)

61-70: TODO comment needs to be addressed.

The validation method has a TODO comment about extending guards. This should be addressed to ensure comprehensive validation.

Would you like me to help implement the extended validation for motor kinds and tank specifics?

src/controllers/flight.py (1)

21-22: Consider adding type hints for better code clarity.

The __init__ method could benefit from explicit return type annotation.

-    def __init__(self):
+    def __init__(self) -> None:
src/api.py (1)

82-84: Consider structuring error response for better client handling.

The current error response might not provide enough structure for clients to handle errors programmatically. Consider returning a structured error object.

-        content=exc_str, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
+        content={
+            "error": {
+                "code": "VALIDATION_ERROR",
+                "message": exc_str,
+                "details": exc.errors()
+            }
+        },
+        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
tests/unit/test_routes/conftest.py (2)

101-102: Consider using an enum for fins_kind parameter.

The fins_kind parameter is using a string literal. Consider using an enum to prevent invalid values and improve type safety.

+from enum import Enum
+
+class FinsKind(str, Enum):
+    TRAPEZOIDAL = 'trapezoidal'
+    # Add other valid kinds here
+
 fins = Fins(
-        fins_kind='trapezoidal',
+        fins_kind=FinsKind.TRAPEZOIDAL,

125-125: Consider using an enum for coordinate_system_orientation parameter.

Similar to the previous suggestion, the coordinate_system_orientation parameter should use an enum to prevent invalid values.

+from enum import Enum
+
+class CoordinateSystemOrientation(str, Enum):
+    TAIL_TO_NOSE = 'tail_to_nose'
+    # Add other valid orientations here
+
-        coordinate_system_orientation='tail_to_nose',
+        coordinate_system_orientation=CoordinateSystemOrientation.TAIL_TO_NOSE,
src/views/motor.py (2)

8-71: Consider grouping related attributes using nested models.

The MotorSimulation class has many attributes that could be logically grouped into nested models for better organization and maintainability.

+class GrainProperties(BaseModel):
+    grain_burn_out: Optional[float] = None
+    grain_density: Optional[float] = None
+    grain_initial_height: Optional[float] = None
+    # ... other grain-related properties
+
+class InertiaProperties(BaseModel):
+    dry_I_11: Optional[float] = None
+    dry_I_12: Optional[float] = None
+    # ... other inertia-related properties
+
 class MotorSimulation(BaseModel):
     message: str = "Motor successfully simulated"
-    # ... current properties
+    grain: GrainProperties
+    inertia: InertiaProperties
+    # ... other grouped properties

15-15: Consider using an enum for coordinate_system_orientation.

The coordinate_system_orientation field uses a string literal default value. Consider using an enum for type safety.

+from enum import Enum
+
+class CoordinateSystemOrientation(str, Enum):
+    NOZZLE_TO_COMBUSTION_CHAMBER = 'nozzle_to_combustion_chamber'
+    # Add other valid orientations here
+
-    coordinate_system_orientation: str = 'nozzle_to_combustion_chamber'
+    coordinate_system_orientation: CoordinateSystemOrientation = CoordinateSystemOrientation.NOZZLE_TO_COMBUSTION_CHAMBER
src/routes/motor.py (2)

34-35: Enhance API documentation with request/response examples.

The current documentation only mentions the argument type. Consider adding example request/response payloads to make the API more user-friendly.

     ## Args
-    ``` models.Motor JSON ```
+    ``` models.Motor JSON ```
+    
+    ## Request Example
+    ```json
+    {
+        "name": "Example Motor",
+        "manufacturer": "Example Manufacturer",
+        "thrust_curve": [...]
+    }
+    ```
+    
+    ## Response Example
+    ```json
+    {
+        "id": "motor123",
+        "name": "Example Motor",
+        "created_at": "2025-02-20T10:00:00Z"
+    }
+    ```

84-94: Consider using 200 status code for binary downloads.

The current implementation uses 203 (Non-Authoritative Information) for binary downloads. While this works, 200 (OK) is more commonly used for successful GET requests returning binary data.

     responses={
-        203: {
+        200: {
             "description": "Binary file download",
             "content": {"application/octet-stream": {}},
         }
     },
-    status_code=203,
+    status_code=200,
     response_class=Response,
src/routes/rocket.py (1)

84-114: Consider extracting common binary download logic.

The binary download endpoint implementation is duplicated across multiple route files. Consider extracting this into a reusable utility function.

Create a new utility file src/utils/binary_response.py:

from fastapi import Response

def create_binary_response(binary: bytes, filename: str) -> Response:
    """Creates a standardized binary response with proper headers."""
    headers = {
        'Content-Disposition': f'attachment; filename="{filename}"'
    }
    return Response(
        content=binary,
        headers=headers,
        media_type="application/octet-stream",
        status_code=200,
    )

Then simplify the endpoint:

-    headers = {
-        'Content-Disposition': f'attachment; filename="rocketpy_rocket_{rocket_id}.dill"'
-    }
     controller = RocketController()
     binary = await controller.get_rocketpy_rocket_binary(rocket_id)
-    return Response(
-        content=binary,
-        headers=headers,
-        media_type="application/octet-stream",
-        status_code=203,
-    )
+    return create_binary_response(binary, f"rocketpy_rocket_{rocket_id}.dill")
src/routes/flight.py (2)

119-136: Consider using PATCH for partial updates.

The endpoints for updating environment and rocket use PUT, which typically implies a complete resource replacement. Since these are partial updates, consider using PATCH instead.

-@router.put("/{flight_id}/environment", status_code=204)
+@router.patch("/{flight_id}/environment", status_code=204)

And similarly for the rocket update endpoint:

-@router.put("/{flight_id}/rocket", status_code=204)
+@router.patch("/{flight_id}/rocket", status_code=204)

158-169: Consider adding query parameters for simulation configuration.

The simulation endpoint could benefit from optional query parameters to configure the simulation behavior.

 @router.get("/{flight_id}/simulate")
-async def get_flight_simulation(flight_id: str) -> FlightSimulation:
+async def get_flight_simulation(
+    flight_id: str,
+    duration: float = 100.0,
+    time_step: float = 0.1,
+    max_altitude: float | None = None,
+) -> FlightSimulation:
     """
     Simulates a flight
 
     ## Args
-    ``` flight_id: Flight ID ```
+    - flight_id: Flight ID
+    - duration: Simulation duration in seconds (default: 100.0)
+    - time_step: Time step for simulation in seconds (default: 0.1)
+    - max_altitude: Optional maximum altitude in meters
     """
     with tracer.start_as_current_span("get_flight_simulation"):
         controller = FlightController()
-        return await controller.get_flight_simulation(flight_id)
+        return await controller.get_flight_simulation(
+            flight_id,
+            duration=duration,
+            time_step=time_step,
+            max_altitude=max_altitude,
+        )
tests/unit/test_routes/test_environments_route.py (2)

137-138: Fix typos in variable names.

The variable name "mock_reponse" is misspelled in multiple places. It should be "mock_response".

Apply this diff to fix the typos:

-    mock_reponse = AsyncMock(return_value=None)
-    mock_controller_instance.put_environment_by_id = mock_reponse
+    mock_response = AsyncMock(return_value=None)
+    mock_controller_instance.put_environment_by_id = mock_response

Also applies to: 156-157, 177-178, 197-198


1-259: Consider adding test cases for edge scenarios.

The test suite is comprehensive but could be enhanced with additional test cases:

  1. Test invalid environment ID format (e.g., non-UUID strings)
  2. Test environment update with empty payload

Example test case for invalid ID format:

def test_read_environment_invalid_id():
    response = client.get('/environments/invalid-id')
    assert response.status_code == 422
    assert response.json() == {'detail': 'Invalid environment ID format'}
tests/unit/test_routes/test_motors_route.py (1)

77-230: Consider refactoring motor type tests to reduce duplication.

The tests for different motor types (generic, liquid, hybrid, solid) share similar structure and assertions. Consider using a parametrized test to reduce code duplication.

Example refactor:

@pytest.mark.parametrize("motor_type,extra_params", [
    ("GENERIC", {
        'chamber_radius': 0,
        'chamber_height': 0,
        'chamber_position': 0,
        'propellant_initial_mass': 0,
        'nozzle_position': 0,
    }),
    ("LIQUID", {'tanks': []}),
    ("HYBRID", {
        'grain_number': 0,
        'grain_density': 0,
        'grain_outer_radius': 0,
        'grain_initial_inner_radius': 0,
        'grain_initial_height': 0,
        'grains_center_of_mass_position': 0,
        'grain_separation': 0,
        'throat_radius': 0,
        'tanks': [],
    }),
    ("SOLID", {
        'grain_number': 0,
        'grain_density': 0,
        'grain_outer_radius': 0,
        'grain_initial_inner_radius': 0,
        'grain_initial_height': 0,
        'grains_center_of_mass_position': 0,
        'grain_separation': 0,
    }),
])
def test_create_motor_types(
    stub_motor_dump,
    mock_controller_instance,
    motor_type,
    extra_params
):
    extra_params['motor_kind'] = motor_type
    stub_motor_dump.update(extra_params)
    mock_response = AsyncMock(return_value=MotorCreated(motor_id='123'))
    mock_controller_instance.post_motor = mock_response
    response = client.post('/motors/', json=stub_motor_dump)
    assert response.status_code == 201
    assert response.json() == {
        'motor_id': '123',
        'message': 'Motor successfully created',
    }
    mock_controller_instance.post_motor.assert_called_once_with(
        MotorModel(**stub_motor_dump)
    )
tests/unit/test_routes/test_rockets_route.py (1)

125-304: Consider refactoring rocket motor type tests to reduce duplication.

The tests for rockets with different motor types share similar structure and assertions. Consider using a parametrized test to reduce code duplication.

Example refactor:

@pytest.mark.parametrize("motor_type,extra_params", [
    ("GENERIC", {
        'chamber_radius': 0,
        'chamber_height': 0,
        'chamber_position': 0,
        'propellant_initial_mass': 0,
        'nozzle_position': 0,
    }),
    ("LIQUID", {'tanks': []}),
    ("HYBRID", {
        'grain_number': 0,
        'grain_density': 0,
        'grain_outer_radius': 0,
        'grain_initial_inner_radius': 0,
        'grain_initial_height': 0,
        'grains_center_of_mass_position': 0,
        'grain_separation': 0,
        'throat_radius': 0,
        'tanks': [],
    }),
    ("SOLID", {
        'grain_number': 0,
        'grain_density': 0,
        'grain_outer_radius': 0,
        'grain_initial_inner_radius': 0,
        'grain_initial_height': 0,
        'grains_center_of_mass_position': 0,
        'grain_separation': 0,
    }),
])
def test_create_rocket_with_motor_types(
    stub_rocket_dump,
    stub_motor_dump,
    mock_controller_instance,
    motor_type,
    extra_params,
    stub_level_tank_dump
):
    extra_params['motor_kind'] = motor_type
    if motor_type in ['LIQUID', 'HYBRID']:
        extra_params['tanks'] = [stub_level_tank_dump]
    stub_motor_dump.update(extra_params)
    stub_rocket_dump.update({'motor': stub_motor_dump})
    mock_response = AsyncMock(return_value=RocketCreated(rocket_id='123'))
    mock_controller_instance.post_rocket = mock_response
    response = client.post('/rockets/', json=stub_rocket_dump)
    assert response.status_code == 201
    assert response.json() == {
        'rocket_id': '123',
        'message': 'Rocket successfully created',
    }
    mock_controller_instance.post_rocket.assert_called_once_with(
        RocketModel(**stub_rocket_dump)
    )
README.md (1)

41-43: Project Structure Documentation: Controllers Update
The addition of interface.py in the controllers directory is a solid move toward abstraction and consistency. However, note that the tree still names the parent directory as lib—ensure this is intentional or update it to src if the restructuring is meant to be complete.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3924dc5 and a46116a.

📒 Files selected for processing (75)
  • Dockerfile (1 hunks)
  • Makefile (1 hunks)
  • README.md (5 hunks)
  • lib/controllers/__init__.py (0 hunks)
  • lib/controllers/environment.py (0 hunks)
  • lib/controllers/flight.py (0 hunks)
  • lib/controllers/motor.py (0 hunks)
  • lib/controllers/rocket.py (0 hunks)
  • lib/models/environment.py (0 hunks)
  • lib/models/flight.py (0 hunks)
  • lib/models/motor.py (0 hunks)
  • lib/models/rocket.py (0 hunks)
  • lib/repositories/__init__.py (0 hunks)
  • lib/repositories/environment.py (0 hunks)
  • lib/repositories/flight.py (0 hunks)
  • lib/repositories/motor.py (0 hunks)
  • lib/repositories/rocket.py (0 hunks)
  • lib/routes/environment.py (0 hunks)
  • lib/routes/flight.py (0 hunks)
  • lib/routes/motor.py (0 hunks)
  • lib/routes/rocket.py (0 hunks)
  • lib/views/environment.py (0 hunks)
  • lib/views/flight.py (0 hunks)
  • lib/views/motor.py (0 hunks)
  • lib/views/rocket.py (0 hunks)
  • pyproject.toml (3 hunks)
  • requirements-dev.txt (1 hunks)
  • src/__init__.py (2 hunks)
  • src/__main__.py (1 hunks)
  • src/api.py (3 hunks)
  • src/controllers/__init__.py (1 hunks)
  • src/controllers/environment.py (1 hunks)
  • src/controllers/flight.py (1 hunks)
  • src/controllers/interface.py (1 hunks)
  • src/controllers/motor.py (1 hunks)
  • src/controllers/rocket.py (1 hunks)
  • src/models/environment.py (1 hunks)
  • src/models/flight.py (1 hunks)
  • src/models/interface.py (1 hunks)
  • src/models/motor.py (1 hunks)
  • src/models/rocket.py (1 hunks)
  • src/models/sub/aerosurfaces.py (3 hunks)
  • src/models/sub/tanks.py (1 hunks)
  • src/repositories/__init__.py (1 hunks)
  • src/repositories/environment.py (1 hunks)
  • src/repositories/flight.py (1 hunks)
  • src/repositories/interface.py (7 hunks)
  • src/repositories/motor.py (1 hunks)
  • src/repositories/rocket.py (1 hunks)
  • src/routes/environment.py (1 hunks)
  • src/routes/flight.py (1 hunks)
  • src/routes/motor.py (1 hunks)
  • src/routes/rocket.py (1 hunks)
  • src/services/environment.py (4 hunks)
  • src/services/flight.py (4 hunks)
  • src/services/motor.py (4 hunks)
  • src/services/rocket.py (5 hunks)
  • src/settings/gunicorn.py (2 hunks)
  • src/utils.py (3 hunks)
  • src/views/environment.py (1 hunks)
  • src/views/flight.py (1 hunks)
  • src/views/interface.py (1 hunks)
  • src/views/motor.py (1 hunks)
  • src/views/rocket.py (1 hunks)
  • tests/test_routes/test_environments_route.py (0 hunks)
  • tests/test_routes/test_flights_route.py (0 hunks)
  • tests/test_routes/test_motors_route.py (0 hunks)
  • tests/test_routes/test_rockets_route.py (0 hunks)
  • tests/unit/test_controllers/test_controller_interface.py (1 hunks)
  • tests/unit/test_repositories/test_repository_interface.py (1 hunks)
  • tests/unit/test_routes/conftest.py (5 hunks)
  • tests/unit/test_routes/test_environments_route.py (1 hunks)
  • tests/unit/test_routes/test_flights_route.py (1 hunks)
  • tests/unit/test_routes/test_motors_route.py (1 hunks)
  • tests/unit/test_routes/test_rockets_route.py (1 hunks)
💤 Files with no reviewable changes (26)
  • lib/controllers/init.py
  • lib/repositories/environment.py
  • tests/test_routes/test_motors_route.py
  • tests/test_routes/test_rockets_route.py
  • lib/models/environment.py
  • lib/repositories/motor.py
  • lib/controllers/flight.py
  • tests/test_routes/test_environments_route.py
  • lib/views/rocket.py
  • tests/test_routes/test_flights_route.py
  • lib/views/environment.py
  • lib/repositories/init.py
  • lib/controllers/rocket.py
  • lib/routes/motor.py
  • lib/views/flight.py
  • lib/views/motor.py
  • lib/models/rocket.py
  • lib/repositories/flight.py
  • lib/models/flight.py
  • lib/models/motor.py
  • lib/repositories/rocket.py
  • lib/routes/environment.py
  • lib/controllers/motor.py
  • lib/routes/flight.py
  • lib/controllers/environment.py
  • lib/routes/rocket.py
✅ Files skipped from review due to trivial changes (6)
  • src/repositories/init.py
  • src/controllers/init.py
  • src/views/interface.py
  • src/main.py
  • src/settings/gunicorn.py
  • src/init.py
🧰 Additional context used
🧠 Learnings (1)
src/routes/motor.py (1)
Learnt from: GabrielBarberini
PR: RocketPy-Team/Infinity-API#38
File: lib/routes/motor.py:75-75
Timestamp: 2024-11-15T15:12:21.314Z
Learning: When modifying API route paths in `lib/routes/motor.py`, changing from `/rocketpy/{motor_id}` to `/{motor_id}/rocketpy` is acceptable when no external consumers are affected. It's acceptable to introduce this breaking change if the team has decided to adopt the new approach, provided that the `README` and related documentation are updated accordingly.
🪛 GitHub Actions: Pylint
src/models/environment.py

[warning] 37-37: C0415: Import outside toplevel (src.views.environment.EnvironmentCreated) (import-outside-toplevel)


[warning] 43-43: C0415: Import outside toplevel (src.views.environment.EnvironmentRetrieved, src.views.environment.EnvironmentView) (import-outside-toplevel)

src/utils.py

[warning] 104-104: W0101: Unreachable code (unreachable)


[warning] 118-118: W0101: Unreachable code (unreachable)


[warning] 132-132: W0101: Unreachable code (unreachable)

src/models/rocket.py

[warning] 50-50: C0415: Import outside toplevel (src.views.rocket.RocketCreated) (import-outside-toplevel)


[warning] 56-56: C0415: Import outside toplevel (src.views.rocket.RocketRetrieved, src.views.rocket.RocketView) (import-outside-toplevel)

src/models/motor.py

[warning] 82-82: C0415: Import outside toplevel (src.views.motor.MotorCreated) (import-outside-toplevel)


[warning] 88-88: C0415: Import outside toplevel (src.views.motor.MotorRetrieved, src.views.motor.MotorView) (import-outside-toplevel)

src/models/flight.py

[warning] 57-57: C0415: Import outside toplevel (src.views.flight.FlightCreated) (import-outside-toplevel)


[warning] 63-63: C0415: Import outside toplevel (src.views.flight.FlightRetrieved, src.views.flight.FlightView) (import-outside-toplevel)

🔇 Additional comments (48)
pyproject.toml (2)

26-29: Establish Testing Environment with Pytest Configurations
The new [tool.pytest.ini_options] section correctly sets the testing parameters by specifying the pythonpath, testpaths, and addopts (using the --import-mode=importlib flag). This configuration should help standardize test discovery and execution.


65-65: Ruff Ignore List Updated with N802
The inclusion of N802 in the [tool.ruff.lint] ignore list relaxes naming convention checks for function and variable names. This is acceptable provided it aligns with the team’s coding style guidelines. It might be beneficial to note this deviation in the project’s style guide to maintain consistency across the codebase.

src/models/interface.py (1)

50-53: Confirm usage of Self type hints
def RETRIEVED(model_instance: type(Self)): relies on Python 3.11’s Self support. If older Python versions are needed, consider a TypeVar or generic pattern.

Would you like me to open a new issue to address type hints for broader compatibility?

src/controllers/interface.py (1)

58-69: Verify concurrency aspects
Fragments of code dynamically generate and call repository methods. In high-concurrency scenarios, ensure that the approach remains thread/async-safe, especially if repository state is shared.

Would you like me to produce a shell script to analyze concurrency usage patterns in the controllers?

src/services/rocket.py (2)

103-112: Implementation aligns with docstring
get_rocket_simulation returns a RocketSimulation as described. The naming and docstring are coherent, and the approach is straightforward.


160-168:

✅ Verification successful

Confirm Python version for match
This pattern matching requires Python 3.10 or newer. Ensure that your deployment environment meets this requirement.


🏁 Script executed:

#!/bin/bash
python --version

Length of output: 32


Python Version Compliance Confirmed

The deployment environment uses Python 3.11.2, which supports the match-case construct required by this code. No modifications are necessary.

src/views/flight.py (1)

1-7: No immediate issues in the imports.
These lines look fine as they import the required modules and types for the file's functionality.

src/repositories/interface.py (5)

36-63: Centralized exception handling is well-structured.
Using a decorator to handle repository exceptions centralizes error handling. However, you might consider providing more informative error responses for PyMongoError and other specific error types to aid debugging clients.


83-112: Assess potential concurrency implications of the singleton pattern.
The repository uses a custom singleton approach guarded by locks. This can get complicated in high-concurrency environments and may lead to bottlenecks or unexpected behavior; consider whether a per-request or context-based repository instance might be safer.


174-175: Check for potential naming collisions in _collection.
Using self.model.NAME to define the collection name is clear. Ensure that model “names” are unique across your codebase; otherwise, collisions might occur in your Mongo schema.


223-227: Check MongoDB update results.
The update_one call does not capture whether any documents were matched or modified. Consider verifying the modified_count to detect outdated or nonexistent records.


246-253: Provide pagination or safe query limits.
find_by_query can potentially fetch large data sets. Consider implementing pagination, query limits, or partial fields to reduce memory usage and response times.
[performance]

src/repositories/motor.py (2)

21-23: Handle potential duplicate inserts.
create_motor simply inserts data. If duplicates are possible, consider indexing or unique checks to avoid confusion or collisions.


28-31: Check update results for changes.
When performing update_motor_by_id, verifying modified_count can help confirm an actual update occurred.

src/repositories/rocket.py (1)

9-19: LGTM! Clean implementation of the repository pattern.

The class follows SOLID principles and properly implements the repository pattern with clear separation of concerns.

src/repositories/environment.py (1)

1-41: Implementation follows consistent repository pattern.

The implementation is consistent with other repository classes, reinforcing the need for a common base implementation.

src/models/sub/tanks.py (1)

18-49: Well-organized parameter grouping.

The clear separation of parameters by tank type and their optional nature makes the model flexible and maintainable. The use of appropriate types and computed parameters is commendable.

Consider adding validators to ensure:

  • Positive values for masses and rates
  • Valid geometry coordinates
  • Compatible mass flow rates
src/controllers/motor.py (1)

10-60: Well-structured controller implementation!

The controller is well-implemented with:

  • Clear documentation
  • Proper error handling using decorators
  • Clean separation of concerns between binary and simulation retrieval
src/controllers/rocket.py (1)

10-21: LGTM! Clean implementation of the ControllerInterface.

The class is well-structured and follows the interface pattern consistently.

src/services/environment.py (1)

45-55: LGTM! Clean implementation of the simulation conversion.

The method effectively converts RocketPy environment attributes to the simulation view model.

src/services/flight.py (2)

8-11: LGTM! Import paths updated to reflect new structure.

The import paths have been correctly updated to use the new "src" directory structure, aligning with the PR's restructuring objectives.


21-21: LGTM! Improved type safety and simplified implementation.

The changes enhance type safety with explicit parameter typing and simplify the equations_of_motion handling.

Also applies to: 38-38

src/views/rocket.py (1)

8-39: LGTM! Well-structured simulation class with comprehensive attributes.

The RocketSimulation class provides a thorough set of attributes with proper type hints and default values. The inheritance from MotorSimulation is appropriate for the domain.

src/views/environment.py (1)

52-63: LGTM! Clean and focused view classes.

The view classes are well-structured with clear responsibilities and consistent messaging.

src/api.py (1)

36-36:

❓ Verification inconclusive

Consider using semantic versioning for version updates.

The version jump from 2.2.0 to 3.0.0 suggests a major breaking change. Ensure this is documented in the changelog and that all breaking changes are properly communicated to API consumers.


🌐 Web query:

What are the best practices for API versioning and when should major version numbers be incremented?

💡 Result:

API versioning is crucial for maintaining compatibility while evolving services. Best practices focus on clear communication, strategic planning, and minimizing disruption to existing users.

Best Practices for API Versioning

1. Use Semantic Versioning (SemVer)
Adopt MAJOR.MINOR.PATCH numbering ([1][6]):

  • Major: Increment for breaking changes (e.g., removing endpoints or altering response structures).
  • Minor: Add backward-compatible features (e.g., new optional fields).
  • Patch: Backward-compatible bug fixes.

2. Version via URI Path
Embed the major version in URLs (e.g., /v1/users) for clarity and caching benefits[2][5]. Avoid exposing minor/patch versions in URIs[2].

3. Prioritize Backward Compatibility

  • Deprecate fields gradually using warnings instead of immediate removal[2][5].
  • For GraphQL APIs, use @deprecated directives[2].

4. Communication & Documentation

  • Notify users 6+ months before deprecating a version[2][5].
  • Publish detailed changelogs and migration guides[1][5][8].

5. Testing & Security

  • Conduct regression tests for new versions[1].
  • Apply security patches to all supported versions[1][8].

When to Increment Major Versions

Major versions must increase when introducing breaking changes such as:

Breaking Change Example Source
Removing/renaming endpoints [2][4][5]
Changing authentication requirements [1][4]
Modifying response schema (e.g., removing fields) [2][4][6]

For non-breaking changes (e.g., adding optional parameters), increment minor or patch versions instead[2][6].

Versioning Strategies Comparison

Method Pros Cons
URI Path (/v1/data) Easy caching/exploration Requires codebase duplication
Headers Clean URLs Harder to test/debug
Query Params Simple implementation Poor cacheability

Sources recommend URI path versioning for most use cases[5].


By following these practices, teams can balance innovation with stability while minimizing friction for API consumers[1][2][5]. Always prioritize backward compatibility where possible and reserve major version increments for unavoidable breaking changes[6][9].

Citations:


API Versioning Validation – Verify Documentation and Intent

The version string in src/api.py (line 36) now reads as follows:

        version="3.0.0",

This major version bump implies breaking changes according to semantic versioning best practices. Please verify that:

  • Changelog Updates: All breaking changes are clearly documented in the changelog, including details on endpoint removals, response schema modifications, or changes to authentication.
  • Consumer Communication: API consumers have been or will be adequately notified about the breaking changes.
  • Version Accuracy: If the changes are not breaking, consider adjusting the versioning (e.g., a minor version bump) to reflect backward-compatible modifications instead.
src/utils.py (2)

13-23: LGTM! Well-structured type conversion function.

The function handles different types appropriately:

  • numpy arrays are converted to lists
  • numpy generic types are converted to their primitive values
  • other types are converted to strings

26-30: LGTM! Clear type alias definition.

The type alias effectively combines validation and serialization using Pydantic decorators.

src/services/motor.py (3)

18-20: LGTM! Clear and organized imports.

The imports are well-organized and follow the new src directory structure.


30-30: LGTM! Improved type safety with MotorModel.

The changes improve type safety by:

  • Using MotorModel instead of MotorView for better type checking.
  • Directly accessing attributes without unnecessary string operations.

Also applies to: 45-46


136-145: LGTM! Clear method renaming and return type update.

The method has been renamed to better reflect its purpose and updated to use the new MotorSimulation type.

tests/unit/test_controllers/test_controller_interface.py (1)

1-228: LGTM! Comprehensive test coverage.

The test file demonstrates excellent practices:

  • Well-organized fixtures for test setup
  • Thorough exception handling tests
  • Complete coverage of CRUD operations
  • Effective use of mocking
tests/unit/test_repositories/test_repository_interface.py (1)

1-287: LGTM! Comprehensive test coverage.

The test file demonstrates excellent practices:

  • Well-organized fixtures for test setup
  • Thorough exception handling tests
  • Complete coverage of repository operations
  • Effective use of mocking and async testing
tests/unit/test_routes/test_flights_route.py (1)

1-366: Well-structured test suite with comprehensive coverage!

The test suite demonstrates excellent practices:

  • Thorough coverage of CRUD operations
  • Consistent error handling patterns
  • Well-organized test fixtures
  • Clear separation of concerns in mocking
requirements-dev.txt (2)

1-1: Dependency Addition: pytest_asyncio
The new dependency for asynchronous testing is a valuable addition in light of upcoming async controllers and services.


7-7: Dependency Addition: black
Adding Black ensures a consistent code format across the project. Consider adding a configuration file (e.g. pyproject.toml) if not already present.

Dockerfile (2)

17-17: Update COPY Command to New Directory
The command now copies the ./src directory into the image, aligning with the refactored project structure. Please verify no old lib references remain.


19-19: Update CMD for Gunicorn Server Launch
The CMD now references src/settings/gunicorn.py and starts the app with src.api:app, which is consistent with the directory changes. Ensure that the Gunicorn configuration in src/settings/gunicorn.py is fully compatible.

Makefile (5)

5-6: Update Code Formatting Command
The black command now targets the ./src directory (with a fallback on ./tests), ensuring that new source files are properly formatted.


9-10: Update Linting Command for Flake8
The lint command now inspects the ./src folder instead of the old directory. Confirm that this change captures all active code while maintaining the ignore patterns.


13-14: Update Static Analysis Command with Pylint
Pylint is now set to check the ./src directory, in sync with the project refactoring. Ensure that any project-specific pylint configurations still apply.


17-18: Update Static Analysis Command with Ruff
The Ruff command has been updated to check ./src. This promotes consistency with the new code layout.


24-24: Update Development Server Command
The dev command now starts the server using src:app—this aligns the Makefile with the refactored module structure.

README.md (6)

28-29: Update Server Start Instructions
The standalone commands for both development and production have been updated to use src:app, which is in line with the new project structure.


60-62: Project Structure Documentation: Repositories Update
Including an interface.py in repositories supports a modular design for data access layers. As with controllers, verify that the directory label reflects the new structure if appropriate.


67-77: Project Structure Documentation: Models Enhancement
The models section now includes an interface.py and introduces a new sub directory with aerosurfaces.py and tanks.py. This enhances modularity and extensibility. Double-check the parent directory name to ensure consistency with the new src structure if applicable.


78-80: Project Structure Documentation: Views Update
The new interface.py added in views standardizes the view layer’s structure. Ensure that similar documentation changes are made across all layers for consistency.


102-107: API Flowchart Enhancements
The API flowchart now includes explicit HTTP status codes for CRUD operations (e.g. 201, 200, 204), which improves clarity for API consumers. Verify that these statuses are correctly implemented on the backend.


129-136: API Flowchart: Simulation Sequence Diagram Update
The simulation diagram now clarifies the process of initializing and simulating the RocketPy native class. This update improves the documentation’s precision—ensure that the implementation aligns with these steps.

GabrielBarberini and others added 3 commits February 22, 2025 19:01
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/utils.py (3)

13-23: Add type hints and docstring for better maintainability.

The function logic is good, but could benefit from:

  1. Type hints for parameters and return value
  2. Docstring explaining the purpose and supported types
  3. Error handling for attribute access

Apply this diff to improve the function:

-def to_python_primitive(v):
+def to_python_primitive(v: Any) -> Any:
+    """Convert complex types to Python primitives.
+    
+    Args:
+        v: Any value, particularly those with a 'source' attribute
+            containing numpy arrays or generic types.
+    
+    Returns:
+        The primitive representation of the input value.
+    """
+    try:
         if hasattr(v, "source"):
             if isinstance(v.source, np.ndarray):
                 return v.source.tolist()
             
             if isinstance(v.source, (np.generic,)):
                 return v.source.item()
             
             return str(v.source)
         
         return str(v)
+    except AttributeError:
+        return str(v)

26-30: Optimize BeforeValidator usage.

The BeforeValidator lambda function lambda v: v is redundant as it simply returns the input value without any transformation.

Consider removing it if no validation is needed:

 AnyToPrimitive = Annotated[
     Any,
-    BeforeValidator(lambda v: v),
     PlainSerializer(to_python_primitive),
 ]

148-148: Remove unnecessary explicit return.

The explicit return statement at the end of the method is unnecessary in Python.

-        return
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a46116a and 469a13e.

📒 Files selected for processing (2)
  • pyproject.toml (3 hunks)
  • src/utils.py (5 hunks)
🔇 Additional comments (7)
pyproject.toml (3)

26-29: Enhance Test Configuration Options

The new [tool.pytest.ini_options] section correctly configures the testing environment by setting:

  • pythonpath to include the repository root,
  • testpaths to run tests from the tests directory, and
  • addopts for the import mode configuration.

This approach standardizes how tests are discovered and executed, promoting consistency across development workflows.


55-57: Linter Configuration Update for Pylint

The additions of no-member, protected-access, and import-outside-toplevel in the disable list are appropriate if these checks are causing undesired false positives in your codebase. Ensure that this aligns with your team’s linting policies.


66-66: Ruff Linter Naming Convention Update

Adding N802 to the ignore list under [tool.ruff.lint] relaxes the naming rule enforcement for functions and variables. This is acceptable if it better fits the project's coding style guidelines.

src/utils.py (4)

5-5: LGTM!

The import statements are well-organized and include all necessary types and validators for the new functionality.

Also applies to: 8-8


98-98: LGTM!

The operator precedence fix correctly clarifies the condition evaluation.


151-151: LGTM!

The simplified type hint improves readability while maintaining the same functionality.


104-104:

❓ Verification inconclusive

Investigate unreachable code warnings.

Multiple instances of # pylint: disable=unreachable suggest that pylint detects unreachable code. This could indicate a potential logic issue in the control flow.

Let's verify the control flow:

Also applies to: 118-118, 132-132


🏁 Script executed:

#!/bin/bash
# Description: Check for similar patterns in the codebase that might help understand the intended flow.

# Search for similar patterns of send calls
rg -A 5 "await.*send.*message.*unreachable"

# Search for related tests
rg -l "send_with_gzip.*test"

Length of output: 1215


Action Required: Revisit Unreachable Code in src/utils.py

The await self.send(message) calls (notably at lines 104, 118, and 132) are marked with # pylint: disable=unreachable. Static analysis shows these patterns consistently appear, which suggests that pylint is flagging these calls as unreachable—possibly due to preceding control flow decisions (e.g., early returns or conditional branches that never fall through).

Please verify that the intended execution paths are correct:

  • Confirm whether these code paths are meant to be unreachable.
  • If they are reachable under specific conditions, adjust the control flow so that pylint’s warning is addressed without needing the disable comment.
  • Otherwise, if they are intentionally unreachable, please document the rationale clearly to make future maintenance easier.

GabrielBarberini and others added 5 commits February 22, 2025 19:05
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/repositories/interface.py (1)

226-230: 🛠️ Refactor suggestion

Align validation approach with insert method.

The assert statement in update_by_id could be bypassed if Python is run with optimizations enabled (-O flag). Consider using the same try-catch validation approach as in the insert method.

-        assert self.model.model_validate(data)
-        await collection.update_one({"_id": ObjectId(data_id)}, {"$set": data})
+        try:
+            self.model.model_validate(data)
+        except ValidationError as e:
+            raise HTTPException(status_code=422, detail=str(e))
+        await collection.update_one({"_id": ObjectId(data_id)}, {"$set": data})
🧹 Nitpick comments (7)
src/controllers/flight.py (2)

24-45: Remove redundant explicit return statement.

The explicit return statement on line 44 is unnecessary as the function implicitly returns None.

        flight = await self.get_flight_by_id(flight_id)
        flight.environment = environment
        await self.update_flight_by_id(flight_id, flight)
-        return

46-67: Consider extracting common update pattern.

This method is very similar to update_environment_by_flight_id. Consider extracting the common pattern into a generic update method to reduce code duplication.

async def _update_flight_attribute(self, flight_id: str, *, attribute_name: str, value: Any) -> None:
    flight = await self.get_flight_by_id(flight_id)
    setattr(flight, attribute_name, value)
    await self.update_flight_by_id(flight_id, flight)

Also, remove the redundant explicit return statement.

        flight = await self.get_flight_by_id(flight_id)
        flight.rocket = rocket
        await self.update_flight_by_id(flight_id, flight)
-        return
src/routes/environment.py (4)

19-23: Consider adding more specific error responses.

While the current error responses cover common scenarios, consider adding more specific error descriptions and response schemas for better API documentation:

  • 404: Add specific cases when this occurs (e.g., "Environment not found")
  • 422: Specify validation error scenarios
  • 500: Include potential internal error cases
     responses={
-        404: {"description": "Not found"},
-        422: {"description": "Unprocessable Entity"},
-        500: {"description": "Internal Server Error"},
+        404: {
+            "description": "Environment not found",
+            "content": {
+                "application/json": {
+                    "example": {"detail": "Environment with ID {id} not found"}
+                }
+            }
+        },
+        422: {
+            "description": "Validation error",
+            "content": {
+                "application/json": {
+                    "example": {"detail": "Invalid environment configuration"}
+                }
+            }
+        },
+        500: {
+            "description": "Internal server error",
+            "content": {
+                "application/json": {
+                    "example": {"detail": "Error processing environment"}
+                }
+            }
+        },
     },

33-38: Enhance API documentation with request/response examples.

The current documentation lacks examples that would help API consumers understand the expected request/response format.

     """
     Creates a new environment
 
     ## Args
-    ``` models.Environment JSON ```
+    ``` models.Environment JSON
+    Example:
+    {
+        "name": "Sample Environment",
+        "latitude": 32.990722,
+        "longitude": -106.975038,
+        "elevation": 1401
+    }
+    ```
+    
+    ## Returns
+    ```
+    {
+        "id": "123e4567-e89b-12d3-a456-426614174000",
+        "name": "Sample Environment",
+        "created_at": "2025-02-20T12:00:00Z"
+    }
+    ```
     """

103-104: Make architecture limitation more prominent.

The architecture limitation is important information that should be more prominently displayed in the documentation.

-    Loads rocketpy.environment as a dill binary.
-    Currently only amd64 architecture is supported.
+    Loads rocketpy.environment as a dill binary.
+    
+    ⚠️ IMPORTANT: This endpoint only supports amd64 architecture.
+    Attempting to use the binary on other architectures will result in errors.

129-134: Consider adding simulation control parameters.

The simulation endpoint could benefit from query parameters to control simulation behavior (e.g., duration, time step, etc.).

     """
     Simulates an environment
 
     ## Args
-    ``` environment_id: Environment ID```
+    ```
+    environment_id: Environment ID
+    
+    Optional Query Parameters:
+    - duration: float (simulation duration in seconds)
+    - time_step: float (simulation time step in seconds)
+    - max_iterations: int (maximum number of iterations)
+    ```
+    
+    ## Returns
+    ```
+    {
+        "environment_id": "123e4567-e89b-12d3-a456-426614174000",
+        "simulation_results": {
+            "duration": 100.0,
+            "time_step": 0.1,
+            "iterations": 1000,
+            "data": [...]
+        }
+    }
+    ```
     """
src/repositories/interface.py (1)

170-171: Consider increasing the minimum connection pool size.

Setting minPoolSize=1 might be insufficient for production workloads, potentially leading to connection establishment overhead during traffic spikes.

-                minPoolSize=1,
+                minPoolSize=3,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 469a13e and aaab1a5.

📒 Files selected for processing (5)
  • src/controllers/flight.py (1 hunks)
  • src/models/sub/tanks.py (1 hunks)
  • src/repositories/interface.py (7 hunks)
  • src/routes/environment.py (1 hunks)
  • src/utils.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils.py
  • src/models/sub/tanks.py
🔇 Additional comments (7)
src/controllers/flight.py (3)

1-23: LGTM! Well-structured imports and class definition.

The imports are well-organized, the class documentation clearly states its purpose, and the inheritance from ControllerInterface aligns with the PR's objective of abstracting common features.


68-90: LGTM! Well-implemented binary retrieval.

The method is well-documented and properly implements the binary retrieval functionality using FlightService.


91-113: LGTM! Well-implemented simulation retrieval.

The method is well-documented and properly implements the simulation retrieval functionality using FlightService.

src/repositories/interface.py (4)

1-22: LGTM! Well-organized imports.

The imports are properly organized and all are necessary for the implementation.


36-62: LGTM! Well-implemented error handling decorator.

The decorator effectively centralizes error handling with proper logging and appropriate HTTP status codes for different error scenarios.


207-214: LGTM! Clean implementation of dynamic repository loading.

The implementation provides a clean way to dynamically load repository classes based on model names.


232-257: LGTM! Well-implemented query methods.

The find and delete methods are properly implemented with appropriate error handling, ID management, and model validation.

@RocketPy-Team RocketPy-Team deleted a comment from coderabbitai bot Feb 22, 2025
Comment on lines +49 to +56
for action in model.METHODS:
method_name = (
f"{action.lower()}_{model.NAME}"
if action == "POST"
else f"{action.lower()}_{model.NAME}_by_id"
)
method = self._generate_method(action.lower(), model)
setattr(self, method_name, method)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why include the model name in the controller methods name? Why not simply make it get_by_id, for instance?

It seems a bit counterintuitive to make use of, since the Controller childs need to know the name of a method that is "runtime defined" (I don't know if there is a better term).

Copy link
Collaborator Author

@GabrielBarberini GabrielBarberini Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the question!

In summary, this is all about a design choice that prioritizes convention over configuration at the cost of some explicitness. I'll tackle your questions in order:

Why not simply make it get_by_id, for instance?

As I said I think it is just a matter of design choice, no hard pros or cons, just a trade off. Some intuition behind it is that since controllers, repositories, views and routes are now tightly orbiting models I think it is reasonable to expect models to be the new pivot for the whole framework.

(...) childs need to know the name of a method that is "runtime defined" (I don't know if there is a better term).

I'm not sure if I got the point of children needing to know the name of the method, but what is happening under the hood is a flavor of Metaprogramming called Reflection. Children controllers don't really knows the name of the methods, they know the name of the model and the model is now the principal entity of the entire framework.

In the future this also enables us to move to automatic route generation like:

  • post_user → POST /user
  • get_user_by_id → GET /user/{id}

@coderabbitai would like to hear your thoughts on that as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GabrielBarberini I think your approach of including model names in method signatures makes sense for this architecture. This design pattern follows the "convention over configuration" principle that has proven successful in frameworks like Ruby on Rails and Django.

Some advantages I see:

  • Self-documenting code: Methods like get_environment_by_id clearly communicate what entity they operate on
  • Collision prevention: When controllers handle multiple models, this prevents method name conflicts
  • Future extensibility: As you mentioned, this pattern aligns well with automatic route generation
  • Consistent mental model: Maintaining the model name throughout the stack (repository, controller, route) creates a consistent pattern

The trade-off with verbosity is reasonable given the clarity it provides, especially as the system grows in complexity. The use of reflection for method generation is also elegant - it allows the framework to be both flexible and predictable.

For developers using this pattern, good IDE tooling with autocomplete will mitigate the discovery challenges that might otherwise exist.


✏️ Learnings added
Learnt from: GabrielBarberini
PR: RocketPy-Team/Infinity-API#44
File: src/controllers/interface.py:49-56
Timestamp: 2025-02-25T21:53:37.155Z
Learning: In the Infinity-API architecture, controller methods are intentionally named with the model name included (e.g., `get_environment_by_id` rather than just `get_by_id`) to maintain a convention over configuration approach, enable future automatic route generation, prevent method name collisions, and provide self-documenting code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@phmbressan
Copy link
Contributor

Partial review only. Some quick other comments:

  1. Just to clarify a bit of terminology, what is being considered a "_Interface" a "_Base" in the repository. Perhaps some extra docs could be introduced.
  2. What are some expected breaking changes from this PR, any route changes?

@GabrielBarberini
Copy link
Collaborator Author

GabrielBarberini commented Feb 25, 2025

Partial review only. Some quick other comments:

  1. Just to clarify a bit of terminology, what is being considered a "_Interface" a "_Base" in the repository. Perhaps some extra docs could be introduced.
  2. What are some expected breaking changes from this PR, any route changes?

Thank you for the prompt review, even if partial!

  1. I just realized I should add more docstrings to these interfaces to explain what they are doing on a higher level. I think this will also facilitate the review for everyone, thanks for raising that! As for the terminology, in Python I think it's hard to think of strict pure interface or abstract classes the same way as in other languages, but definitely this is not a pure interface, it is an interface in the sense that it is a thin slice of code whose sole purpose is to get a model plugged and expose a panel of common features for the client. Still I think I could change the name of the class to ControllerBase instead, so one knows it is actually meant to inherit it. Will do that with the docstring commit.

  2. There are indeed breaking changes, that is why I had to raise major version. Basically some exit contracts are now different: e.g different HTTP status response code to better meet REST API standards, different path for making calls on known entities, also we are no longer using query parameter to set the motor kinds as before, now we require motor_kind to be explicitly set in the body since this information is crucial for the client when the model is retrieved back (so it gets smoothly propagated to the db without any computed parameter).

@GabrielBarberini GabrielBarberini requested a review from Copilot March 2, 2025 17:12
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Overview

This PR refactors the API V3 implementation by removing legacy controllers, repositories, models, and route files and replacing them with interface-based abstractions while updating route semantics and documentation. Key changes include:

  • Updating the README with new module paths and revised endpoints.
  • Adding interface.py files in controllers, repositories, models, and views.
  • Removing legacy implementation files in favor of the new abstracted design.

Reviewed Changes

File Description
README.md Updates to standalone commands, endpoint response codes, and docs.
lib/routes/.py, lib/controllers/.py, etc. Removal of legacy implementation files in favor of interface abstractions.

Copilot reviewed 76 out of 76 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (2)

README.md:131

  • [nitpick] The endpoint 'GET model/:id/simulate/' is missing a leading slash compared to other endpoints. Consider updating it to '/model/:id/simulate/' for consistency.
User ->> API: GET model/:id/simulate/

lib/routes/flight.py:1

  • This legacy route file has been completely removed. Please ensure that its functionality is fully covered by the new interface-based implementation and that corresponding tests have been updated accordingly.
diff --git a/lib/routes/flight.py b/lib/routes/flight.py

Makefile Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a job on GitHub Actions to automatically run all these tests every Pull Request?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai please create a gh issue with that specification so we can address it later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for setting up GitHub Actions to run tests on PRs

gh issue create \
  --title "Set up GitHub Actions CI workflow for automated testing on PRs" \
  --body "## Overview
As discussed in PR #44, we need to set up a GitHub Actions workflow to automatically run all tests on each Pull Request.

## Specifications
The workflow should:
- Trigger on each PR and push to main branch
- Run the following commands from the Makefile:
  - \`black\` for code formatting check
  - \`flake8\` for linting
  - \`pylint\` for static code analysis
  - \`ruff\` for additional linting/checking
  - \`pytest\` for running all tests
- Fail the check if any of these commands fail
- Show test results in the PR

## Additional considerations
- Consider caching dependencies to speed up workflow
- Consider using separate jobs for linting vs testing
- Ensure it works with the new source directory structure (\`src\` instead of \`lib\`)

## Reference
- Originally requested by @Gui-FernandesBR in PR #44
- URL: https://github.com/RocketPy-Team/Infinity-API/pull/44#discussion_r1978372522" \
  --assignee "GabrielBarberini"

Length of output: 1076


@GabrielBarberini I've created a GitHub issue as requested for setting up GitHub Actions to run automated tests on each PR. The issue has been assigned to you and can be found here: #45

The issue includes specifications for running all the tests from the Makefile (black, flake8, pylint, ruff, and pytest) when PRs are created or updated. I've also included some additional considerations about caching dependencies and ensuring compatibility with the new src directory structure.

(_/)
(•ᴥ•)
/ >📝

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the kind of nosecone could easily be an enum. However, I think we should define this enum within the lib itself

Comment on lines +52 to +54
interpolation_method: Literal[
'linear', 'spline', 'akima', 'polynomial', 'shepard', 'rbf'
] = 'linear'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you're going to need these "interpolation options" elsewhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far we don't so I rather keep it like this if you don't mind.

Gui-FernandesBR
Gui-FernandesBR previously approved these changes Mar 4, 2025
Copy link
Member

@Gui-FernandesBR Gui-FernandesBR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com>
GabrielBarberini and others added 2 commits March 8, 2025 10:21
Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com>
Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (12)
src/models/flight.py (3)

7-9: Consider separating HTTP method constants from the data model.

It might be more logical to keep route-related metadata (like METHODS) in controllers or routing modules, leaving the model focused solely on data structure and validation.


22-22: Address the TODO comment.

There's a # TODO: implement initial_solution marker. Ensure it doesn't get overlooked in later iterations.

Do you want me to propose a sample implementation or open a new issue to track this?


30-45: Refine or centralize excluded fields configuration.

get_additional_parameters currently hardcodes a list of fields to exclude. For maintainability, consider centralizing these fields in a class-level constant or using a pydantic field configuration if suitable, simplifying future changes.

tests/unit/test_routes/test_flights_route.py (1)

60-366: Comprehensive test coverage with uniform patterns.

You've tested normal, invalid, not found, and server error scenarios for flight creation, retrieval, updates, and deletions. This thorough coverage is commendable. For additional maintainability, consider parametrizing repeated 404 and 500 test patterns to reduce duplication.

tests/unit/test_routes/test_environments_route.py (2)

25-37: Consider using an async test client or pytest.mark.asyncio for better realism.
While TestClient works for simplicity, your controller methods are asynchronous (AsyncMock usage). To ensure you're accurately testing async behavior end-to-end, consider leveraging an async test client (e.g., async with AsyncClient(...)) or marking tests with pytest.mark.asyncio.


137-144: Parametrize the “not found” or “server error” scenarios for DRY tests.
You replicate a similar structure in multiple tests (e.g., 404, 500 errors). Consider using pytest.mark.parametrize to unify repeated error-scenario tests, reducing code duplication.

src/routes/motor.py (2)

29-40: Possible improvement: inject MotorController rather than instantiating it inside each route.
Currently, each route creates a new MotorController() instance. For better testability and a cleaner design, consider injecting the controller as a dependency (e.g., via FastAPI’s dependency injection or a factory function).


84-95: Mention or verify multi-architecture support in future.
The docstring explicitly says "Currently only amd64 architecture is supported." This is acceptable for now, but you might consider clarifying (in docs or code) if additional architectures will be supported later.

src/routes/rocket.py (2)

29-40: Consider a shared pattern with motor.py to reduce duplication.
Routes in motor.py and rocket.py follow nearly the same CRUD pattern. Extracting shared logic or using a base router could improve maintainability without rewriting the same error handling and docstrings in each file.


117-128: Ensure rocket simulation route covers erroneous or partial states.
You’ve covered the happy path for simulation. To be thorough, consider adding fallback or error-handling coverage for incomplete rocket specs or invalid rocket_id.

tests/unit/test_routes/test_motors_route.py (2)

250-259: Inconsistent mock error setup across tests.

There's inconsistency in how HTTP exceptions are mocked across different test functions. Some tests use direct AsyncMock(side_effect=...) pattern while others assign to the side_effect attribute later.

For consistency, consider standardizing on one approach throughout the test file. For example:

-    mock_response = AsyncMock(side_effect=HTTPException(status_code=500))
-    mock_controller_instance.post_motor = mock_response
+    mock_controller_instance.post_motor.side_effect = HTTPException(status_code=500)

Or consistently use the current approach:

    mock_response = AsyncMock(side_effect=HTTPException(status_code=500))
    mock_controller_instance.post_motor = mock_response

274-289: Consider using HTTPStatus constants for status codes.

Using numeric literals (404, 500) for HTTP status codes makes the code less readable and maintainable.

Consider using constants from status module for HTTP status codes, as seen in the test_rockets_route.py file:

-    mock_response = AsyncMock(side_effect=HTTPException(status_code=404))
+    mock_response = AsyncMock(side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND))

-    assert response.status_code == 404
+    assert response.status_code == status.HTTP_404_NOT_FOUND
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aaab1a5 and 54c8ce8.

📒 Files selected for processing (10)
  • src/models/environment.py (1 hunks)
  • src/models/flight.py (1 hunks)
  • src/routes/environment.py (1 hunks)
  • src/routes/flight.py (1 hunks)
  • src/routes/motor.py (1 hunks)
  • src/routes/rocket.py (1 hunks)
  • tests/unit/test_routes/test_environments_route.py (1 hunks)
  • tests/unit/test_routes/test_flights_route.py (1 hunks)
  • tests/unit/test_routes/test_motors_route.py (1 hunks)
  • tests/unit/test_routes/test_rockets_route.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/flight.py
  • src/models/environment.py
🧰 Additional context used
🧠 Learnings (1)
src/routes/motor.py (1)
Learnt from: GabrielBarberini
PR: RocketPy-Team/Infinity-API#38
File: lib/routes/motor.py:75-75
Timestamp: 2024-11-15T15:12:21.314Z
Learning: When modifying API route paths in `lib/routes/motor.py`, changing from `/rocketpy/{motor_id}` to `/{motor_id}/rocketpy` is acceptable when no external consumers are affected. It's acceptable to introduce this breaking change if the team has decided to adopt the new approach, provided that the `README` and related documentation are updated accordingly.
🔇 Additional comments (13)
src/models/flight.py (2)

47-54: Clarify the intent of empty static methods.

UPDATED and DELETED return None. If they’re placeholders, please add docstrings or raise a NotImplementedError to communicate their future usage or significance.


57-63: Fix circular imports in static methods.

This is the same concern raised in a previous review. To avoid potential circular imports, move these imports to the top of the file or rely on forward references in type hints.

tests/unit/test_routes/test_flights_route.py (1)

1-20: Great use of FastAPI TestClient and fixtures.

The initial setup for mocking controllers and using fixtures is well structured, making the tests clear and maintainable.

tests/unit/test_routes/test_environments_route.py (2)

40-54: Test coverage appears solid; ensure stub_environment_dump is defined and verified.
The test references stub_environment_dump from a fixture, but it's not shown here. Confirm that it is declared elsewhere (like conftest.py) and thoroughly tested to avoid silent test failures if renamed or removed.


81-86: Great negative test coverage for validation errors.
This test ensures the correct 422 response for invalid input. The approach is concise and confirms robust validation logic.

src/routes/motor.py (1)

96-115: Binary download endpoint is well-structured.
Clearly returning a binary response with correct headers is a best practice. The usage of a custom filename in Content-Disposition is a nice detail.

src/routes/rocket.py (1)

84-95: Consistent binary serving approach.
Like the motor route, you’re specifying a well-formed binary response with headers. This consistency across modules is good practice.

src/routes/environment.py (2)

90-122: Environment binary response is well-structured.

The implementation for the binary file download endpoint is correctly configured with appropriate headers, media type, and status code. It's good to see that the status code has been set to 200 (OK) as it's the standard for successful responses with the requested content.


125-138: Well-defined environment simulation endpoint.

The simulation endpoint is structured appropriately with clear documentation and proper use of OpenTelemetry for tracing. The controller pattern is used consistently with other endpoints in this file.

tests/unit/test_routes/test_motors_route.py (1)

240-248: Good test for model validation after instantiation.

Testing validation that occurs after model instantiation catches errors that simple input validation would miss. This is a valuable test case.

tests/unit/test_routes/test_rockets_route.py (3)

312-321: Well-structured error handling test.

Good approach using HTTPException with status constants from the status module. This is more maintainable than using numeric status codes directly.


445-455: Binary content test looks good.

This test correctly validates both the binary content and the content-type header in the response, which is important for binary data endpoints.


357-366:

✅ Verification successful

Verify motor_kind is set before test execution.

In line 358, you're updating the rocket's motor kind to 'SOLID' without first verifying that the stub_rocket_dump contains a 'motor' key. This could potentially cause a KeyError if the fixture doesn't initialize the motor dictionary.

Make sure the fixture stub_rocket_dump includes a 'motor' key with at least an empty dictionary:


🏁 Script executed:

#!/bin/bash
# Check if the stub_rocket_dump fixture initializes the motor dictionary
grep -A20 "def stub_rocket_dump" tests/unit/conftest.py

Length of output: 113


I'll perform an additional repository-wide search for the stub_rocket_dump fixture definition to determine whether it's defined elsewhere and if it includes the expected 'motor' key. This will help us verify if there's a potential risk of a KeyError when updating 'motor_kind'.

Once we have the output from this script, we can decide whether the test should be updated to first verify that a 'motor' key exists or if the fixture needs adjustments. Please run the above script and share the results so that we can finalize the review comment.


🏁 Script executed:

#!/bin/bash
# Search the entire repository for the definition of stub_rocket_dump
grep -R -C 5 "def stub_rocket_dump" .

Length of output: 851


Confirmation: Fixture Ensures 'motor' Key Existence

After investigating the definition of the stub_rocket_dump fixture in tests/unit/test_routes/conftest.py, we determined that the fixture constructs the rocket with a motor field using the stub_motor_dump dependency. As long as stub_motor_dump reliably returns a dictionary, the 'motor' key will be present, and updating its 'motor_kind' value in the test will not raise a KeyError.

  • The stub_rocket_dump fixture assigns its motor using motor=stub_motor_dump.
  • Verify that stub_motor_dump continues to return a valid dictionary. If this changes in the future, consider adding an explicit assertion (e.g., assert 'motor' in stub_rocket_dump) before updating its contents.

No modifications are required to the test code as implemented.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (14)
src/controllers/motor.py (2)

10-17: Clarify class purpose in docstring.
The high-level documentation is concise, but consider elaborating on the scope of responsibilities (e.g., if it also orchestrates external dependencies). This can help new contributors quickly understand the controller’s role.


22-42: Validate motor existence handling.
Currently, a missing motor raises a 404, but additional logging or context (e.g., including the motor_id in the error detail) can aid debugging if multiple services query this endpoint.

src/controllers/flight.py (3)

12-19: Expand on route usage in docstring.
Although the docstring references CRUD for Flight, specifying which endpoints rely on these methods can help clarify how external clients interact with them.


68-90: Add explanation for binary serialization format.
The docstring mentions returning a dill binary, but clarifying how or why dill is used (versus other serialization methods) may benefit maintainers and future debugging of potential unpickling issues.


91-113: Consider partial or on-demand data retrieval.
Simulations for large datasets might be expensive or memory-intensive. If you anticipate large flight data, you could explore partial retrieval or chunking.

src/controllers/interface.py (5)

12-36: Consider refining final log statements.
The final log line logs "Call to method completed" even if an exception occurred. You could split this into success/failure logs to improve diagnostic clarity.


45-57: Ensure consistency between model actions and methods.
By generating method names (e.g., post_MyModel and get_MyModel_by_id), the code strongly depends on the naming conventions in model.METHODS. Consider adding validation or warnings if the model actions are not recognized, to avoid silent mismatches.


83-100: Explicitly log resource not found.
When model_instance is None, you raise an HTTP 404. You could also log the specific model_id to help triage failures when large volumes of requests come in.


101-114: Clarify concurrency expectations.
If multiple requests can update the same record simultaneously, confirm that your repository method (update_{model.NAME}_by_id) handles concurrency (e.g., optimistic locking).


115-125: Document delete conditions.
Betting on a simple 404 or success might not be enough for certain domain contexts. If important relationships exist, a more detailed check or a soft-delete approach might be warranted.

tests/unit/test_controllers/test_controller_interface.py (4)

117-131: Consider simplifying the test setup with a fixture.

While the test is correct, the nested patch structure makes it somewhat harder to read. Consider extracting the common mocking setup to a fixture for cleaner tests.

-@pytest.mark.asyncio
-async def test_controller_interface_generate_available_method(
-    stub_controller, stub_model
-):
-    with patch('src.controllers.interface.RepositoryInterface') as mock_repo:
-        with patch(
-            'src.controllers.interface.ControllerBase._get_model'
-        ) as mock_get:
-            mock_get.return_value = stub_model
-            method = stub_controller._generate_method('get', stub_model)
-            assert (
-                await method(None, stub_model, mock_repo, 'arg', key='bar')
-                == stub_model
-            )
+@pytest.fixture
+def mocked_repository():
+    with patch('src.controllers.interface.RepositoryInterface') as mock_repo:
+        yield mock_repo
+
+@pytest.mark.asyncio
+async def test_controller_interface_generate_available_method(
+    stub_controller, stub_model, mocked_repository
+):
+    with patch(
+        'src.controllers.interface.ControllerBase._get_model'
+    ) as mock_get:
+        mock_get.return_value = stub_model
+        method = stub_controller._generate_method('get', stub_model)
+        assert (
+            await method(None, stub_model, mocked_repository, 'arg', key='bar')
+            == stub_model
+        )

143-156: Consider a more direct assertion for the post model test.

The test is effective but could benefit from more explicit verification of the repository interaction.

@pytest.mark.asyncio
async def test_controller_interface_post_model(stub_controller, stub_model):
    with patch('src.controllers.interface.RepositoryInterface') as mock_repo:
        with patch(
            'src.controllers.interface.RepositoryInterface.get_model_repo'
        ) as mock_get_repo:
            mock_get_repo.return_value = mock_repo
+           mock_repo_instance = mock_repo.return_value.__aenter__.return_value
+           mock_repo_instance.create_test_model.return_value = None
            assert (
                await stub_controller._post_model(
                    stub_model, mock_repo, stub_model
                )
                == 'Created'
            )
+           mock_repo_instance.create_test_model.assert_called_once_with(stub_model)

167-169: Simplify repository mock setup for better readability.

The long chain of mock setups makes the code harder to read. Consider creating a helper variable for the mock.

-mock_repo.return_value.__aenter__.return_value.read_test_model_by_id.return_value = (
-    stub_model
-)
+mock_repo_instance = mock_repo.return_value.__aenter__.return_value
+mock_repo_instance.read_test_model_by_id.return_value = stub_model

1-228: Consider adding tests for edge cases.

The test suite is comprehensive for the happy path and basic error handling, but could benefit from testing edge cases like:

  • Empty model list initialization
  • Models without required attributes (NAME, METHODS)
  • Models with only partial method support
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54c8ce8 and 9ba7aa2.

📒 Files selected for processing (6)
  • src/controllers/environment.py (1 hunks)
  • src/controllers/flight.py (1 hunks)
  • src/controllers/interface.py (1 hunks)
  • src/controllers/motor.py (1 hunks)
  • src/controllers/rocket.py (1 hunks)
  • tests/unit/test_controllers/test_controller_interface.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/controllers/rocket.py
  • src/controllers/environment.py
🔇 Additional comments (16)
src/controllers/motor.py (1)

43-59: Confirm simulation side effects.
When retrieving simulations or motor binaries, ensure there are no hidden side effects in the MotorService (e.g., caching or queuing tasks). If so, document them clearly for maintainers.

src/controllers/flight.py (2)

24-45: Perform input validation on environment.
Before assigning environment to the flight, consider ensuring it is valid or complete. This prevents partially updated or malformed records from being written to the database.


46-67: Ensure rocket references are updated consistently.
Parallel to validating the environment, confirm that the rocket model is properly verified or tested. If the rocket is missing mandatory fields, this might lead to unexpected behavior downstream.

src/controllers/interface.py (1)

58-69: Handle unknown actions gracefully.
The code raises NotImplementedError if _action_model is not found. Ensure this is documented so developers realize they must define the handler in ControllerBase for new actions (e.g., PATCH).

tests/unit/test_controllers/test_controller_interface.py (12)

1-9: LGTM, appropriate imports for controller interface testing.

The imports are well-organized, pulling in the necessary modules for mocking, testing, and handling MongoDB and HTTP exceptions.


11-20: Well-structured test fixture.

The stub_model fixture creates a comprehensive mock with all the necessary attributes and lambda functions required for testing the controller interface.


23-25: Clean controller fixture implementation.

This fixture effectively initializes a ControllerBase with the stub_model for testing.


28-47: Good test coverage for the happy path scenario.

The test properly verifies that when no exception occurs, the controller_exception_handler preserves the original method's return value and logs appropriately.


50-64: Complete test for database exception handling.

This test effectively verifies that PyMongoError is properly converted to a 503 HTTP exception with the appropriate message and logging.


66-80: LGTM - HTTP exception pass-through test.

The test confirms that HTTPExceptions are passed through without modification and no error logging occurs.


82-95: Good coverage of unexpected exception handling.

The test verifies that unexpected exceptions like ValueError are correctly converted to 500 HTTP exceptions with appropriate logging.


98-114: Complete test for controller initialization.

The test comprehensively verifies that the controller initializes correctly, registering models and generating the expected methods.


133-141: LGTM, properly tests unavailable method handling.

The test correctly verifies that attempting to generate an unsupported method raises NotImplementedError.


176-192: Complete test for model not found scenario.

This test properly verifies that when a model is not found, an HTTP 404 exception is raised with the appropriate message.


194-210: Good test for model update functionality.

The test ensures that the update operation returns the expected result and interacts correctly with the repository.


212-227: Well-structured test for delete model operation.

The test correctly verifies the delete operation's behavior and return value.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/models/interface.py (1)

19-24: ⚠️ Potential issue

Remove deprecated validate_all_in_root from model_config.

Pydantic v2 no longer supports validate_all_in_root. Leaving this key may lead to runtime errors.

Please remove it:

 model_config = ConfigDict(
     use_enum_values=True,
     validate_default=True,
-    validate_all_in_root=True,
     validate_assignment=True,
 )
src/repositories/interface.py (1)

237-237: 🛠️ Refactor suggestion

Replace assert-based validation with explicit handling.

Using assert self.model.model_validate(data) can be bypassed if Python is run with optimizations (-O). Explicitly validate and handle or raise instead.

- assert self.model.model_validate(data)
+ try:
+     self.model.model_validate(data)
+ except ValidationError as e:
+     raise HTTPException(status_code=422, detail=str(e))
🧹 Nitpick comments (2)
src/models/interface.py (1)

26-30: Add type annotations to improve clarity and maintainability.

Methods set_id and get_id do not have return or parameter type annotations. Explicit type hints will help maintain code clarity.

Example:

- def set_id(self, value):
+ def set_id(self, value: str) -> None:

- def get_id(self):
+ def get_id(self) -> Optional[str]:
     return self._id
src/repositories/interface.py (1)

183-183: Consider making the database name configurable.

Hardcoding rocketpy as the database name can limit flexibility. Using a configurable setting would allow overriding in different environments (e.g., dev, staging, prod).

- self._collection = self._client.rocketpy[self.model.NAME]
+ database_name = Secrets.get_secret("DATABASE_NAME", default="rocketpy")
+ self._collection = self._client[database_name][self.model.NAME]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba7aa2 and 499f505.

📒 Files selected for processing (3)
  • src/controllers/interface.py (1 hunks)
  • src/models/interface.py (1 hunks)
  • src/repositories/interface.py (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/models/interface.py (2)
Learnt from: GabrielBarberini
PR: RocketPy-Team/Infinity-API#44
File: src/models/interface.py:12-17
Timestamp: 2025-02-22T21:53:21.139Z
Learning: In Pydantic v2, both bare dict and ConfigDict can be used for model_config, but ConfigDict is recommended for better type hints and IDE support.
Learnt from: GabrielBarberini
PR: RocketPy-Team/Infinity-API#44
File: src/models/interface.py:12-17
Timestamp: 2025-02-22T21:53:21.139Z
Learning: Pydantic v2 requires using ConfigDict for model configuration. Plain dictionaries lack type safety, proper validation, and full feature support.
🔇 Additional comments (1)
src/controllers/interface.py (1)

91-93: Avoid constructing repo method names with string interpolation.

Using await getattr(repo, f'create_{model.NAME}') can break if naming conventions change. Consider a centralized registry or map that validates permissible actions, or directly call a known method on the repository.

- inserted_id = await getattr(repo, f'create_{model.NAME}')(model_instance)
+ # Potential approach using a method map:
+ create_method = getattr(repo, "create", None)
+ if create_method is None:
+     raise NotImplementedError("create method not defined on repository")
+ inserted_id = await create_method(model.NAME, model_instance)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
requirements.txt (1)

5-5: Addition of uvloop Dependency

The new dependency uvloop has been added to support the updated Gunicorn command in the Dockerfile. Consider pinning it to a specific version for consistency and reproducible builds, similar to how numpy is version-locked.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 499f505 and 5bcd592.

📒 Files selected for processing (2)
  • Dockerfile (1 hunks)
  • requirements.txt (1 hunks)
🔇 Additional comments (2)
Dockerfile (2)

17-17: Update Source Directory Copy Instruction

The Dockerfile now copies the src directory (COPY ./src /app/src) instead of the old lib directory. This change aligns the build context with the new project structure. Ensure that the entire source code and necessary resources are correctly located under src.


19-19: Revise Gunicorn Startup Command

The updated CMD instruction now references the new configuration file (src/settings/gunicorn.py) and application module (src.api:app), and it includes the --loop argument with uvloop for enhanced event loop performance. Confirm that these changes have been tested across all deployment environments and that all documentation has been updated accordingly.

Copy link
Member

@Gui-FernandesBR Gui-FernandesBR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my side

64/77 files LGTM

Good luck, great work!!

@GabrielBarberini GabrielBarberini merged commit e28aa70 into master Mar 8, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants