Skip to content

Conversation

@GabrielBarberini
Copy link
Collaborator

@GabrielBarberini GabrielBarberini commented Jun 1, 2025

Pull Request Overview

This PR shifts the simulation encoding approach to utilize the official RocketPy encoders and a new DiscretizeConfig for improved dynamic attribute support across simulation models. Key changes include:

  • Replacing usage of get_instance_attributes with the new rocketpy_encoder and DiscretizeConfig.
  • Updating simulation view classes (RocketSimulation, MotorSimulation, FlightSimulation, EnvironmentSimulation) to use pydantic’s ConfigDict.
  • Refactoring services to integrate the updated encoding logic for Rocket, Motor, Flight, and Environment simulations.

Summary by CodeRabbit

  • New Features

    • Added a configurable RocketPy encoder and DiscretizeConfig to produce encoded/discretized object data.
  • Refactor

    • Simulations and view models now use the encoder, accept dynamic extra fields, and expose streamlined, extensible attributes for environment, flight, motor, and rocket.
    • Date defaults switched to timezone-aware factories.
  • Chores

    • JSON outputs and repository payloads now omit null fields for cleaner responses and storage.
  • Documentation

    • Added/expanded docstrings describing dynamic encoding and model behavior.

@GabrielBarberini GabrielBarberini requested review from Copilot and removed request for aasitvora99 June 1, 2025 03:02
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 1, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@GabrielBarberini has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between a26e01c and 2510936.

📒 Files selected for processing (8)
  • pyproject.toml (1 hunks)
  • src/models/environment.py (2 hunks)
  • src/services/environment.py (2 hunks)
  • src/utils.py (3 hunks)
  • src/views/environment.py (2 hunks)
  • src/views/flight.py (1 hunks)
  • src/views/motor.py (1 hunks)
  • src/views/rocket.py (1 hunks)

Walkthrough

Replaces generic attribute extraction with a RocketPy-aware encoder (rocketpy_encoder + DiscretizeConfig) across services; updates simulation view models to accept dynamically encoded attributes; changes serialization to omit None fields in API models/views and repository payloads.

Changes

Cohort / File(s) Change Summary
Services
src/services/environment.py, src/services/flight.py, src/services/motor.py, src/services/rocket.py
Replaced get_instance_attributes(...) with rocketpy_encoder(self.<obj>, DiscretizeConfig.for_<type>()); updated imports to use rocketpy_encoder and DiscretizeConfig.
Encoder utils
src/utils.py
Added DiscretizeConfig and rocketpy_encoder; removed to_python_primitive and AnyToPrimitive; encoder deep-copies objects, discretizes Function attributes, encodes via RocketPyEncoder with post-processing and fallback stringification; adjusted middleware/minimum_size default.
Views — simulations
src/views/environment.py, src/views/flight.py, src/views/motor.py, src/views/rocket.py
Models now allow extra dynamic fields: added model_config = ConfigDict(extra='allow', arbitrary_types_allowed=True, ser_json_exclude_none=True where applied); widened many function-related types from AnyToPrimitive to Any; reorganized fields, added docstrings and some classes now inherit ApiBaseView or expose new API view wrappers.
API / interface config
src/models/interface.py, src/views/interface.py
Added ser_json_exclude_none=True to model_config on ApiBaseModel/ApiBaseView so JSON serialization omits None fields.
Repositories
src/repositories/flight.py, src/repositories/motor.py, src/repositories/rocket.py
Create/update now call .model_dump(exclude_none=True) to omit None-valued fields from DB insert/update payloads.

Sequence Diagram(s)

sequenceDiagram
    participant Service as Service (e.g., FlightService)
    participant Obj as RocketPy Object
    participant Config as DiscretizeConfig
    participant Encoder as rocketpy_encoder
    participant View as SimulationView

    Service->>Obj: read instance (self.<obj>)
    Service->>Config: DiscretizeConfig.for_<type>()
    Service->>Encoder: rocketpy_encoder(Obj, Config)
    note right of Encoder #eef: copy object, discretize Function attributes\nsample functions within bounds/samples
    Encoder->>Encoder: serialize copy via RocketPyEncoder (include functions/outputs)
    Encoder-->>Service: return encoded dict
    Service->>View: instantiate SimulationView(**encoded dict)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • luimot
  • phmbressan
  • Gui-FernandesBR
  • aasitvora99

Poem

"I nibble bytes and hop through code,
Functions sampled down a tidy road.
Encoder hums and fields align,
Views take shapes I helped define.
🐰🚀 — rabbit-approved commit."

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rocketpy_encoders_refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

This comment was marked as duplicate.

coderabbitai[bot]

This comment was marked as outdated.

@GabrielBarberini GabrielBarberini requested a review from luimot June 1, 2025 03:11
@GabrielBarberini GabrielBarberini requested a review from Copilot June 1, 2025 03:13

This comment was marked as duplicate.

@GabrielBarberini GabrielBarberini requested a review from Copilot June 1, 2025 03:23

This comment was marked as outdated.

@GabrielBarberini GabrielBarberini requested a review from Copilot June 1, 2025 03:32

This comment was marked as outdated.

@GabrielBarberini
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 1, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

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.

Pull Request Overview

This PR modernizes the simulation encoding approach by replacing the deprecated get_instance_attributes utility with official RocketPy encoders and introducing a new DiscretizeConfig class for better dynamic attribute handling. The changes enable more flexible and extensible simulation data serialization across all RocketPy object types.

Key Changes:

  • Replaced get_instance_attributes with new rocketpy_encoder function using official RocketPy RocketPyEncoder
  • Added DiscretizeConfig class for configurable function discretization parameters
  • Updated all simulation view classes to use Pydantic's ConfigDict with extra='allow' for dynamic attributes

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/utils.py Added DiscretizeConfig class and rocketpy_encoder function replacing deprecated encoding approach
src/views/*.py Updated simulation view classes to use ConfigDict and replaced specific attribute types with Any
src/services/*.py Replaced get_instance_attributes calls with rocketpy_encoder using appropriate configs
src/repositories/*.py Added exclude_none=True to model_dump() calls for cleaner serialization
src/models/interface.py Added ser_json_exclude_none=True to base model configuration

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: 2

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8450082 and d1f9a97.

📒 Files selected for processing (5)
  • src/models/interface.py (1 hunks)
  • src/repositories/flight.py (1 hunks)
  • src/repositories/motor.py (1 hunks)
  • src/repositories/rocket.py (1 hunks)
  • src/views/interface.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-02-22T21:53:21.139Z
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.

Applied to files:

  • src/views/interface.py
📚 Learning: 2025-02-22T21:53:21.139Z
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.

Applied to files:

  • src/views/interface.py
🧬 Code Graph Analysis (3)
src/repositories/rocket.py (4)
src/repositories/interface.py (2)
  • insert (225-232)
  • update_by_id (235-239)
src/models/rocket.py (1)
  • RocketModel (13-63)
src/routes/rocket.py (1)
  • update_rocket (56-68)
tests/unit/test_routes/test_rockets_route.py (1)
  • test_update_rocket (357-365)
src/repositories/flight.py (2)
src/repositories/interface.py (2)
  • insert (225-232)
  • update_by_id (235-239)
src/models/flight.py (2)
  • FlightModel (7-71)
  • DELETED (53-54)
src/repositories/motor.py (5)
src/repositories/interface.py (3)
  • insert (225-232)
  • find_by_id (242-249)
  • update_by_id (235-239)
src/models/motor.py (2)
  • MotorModel (16-95)
  • UPDATED (73-74)
tests/unit/test_routes/test_motors_route.py (2)
  • test_update_motor_invalid_input (301-305)
  • test_update_motor (291-298)
src/routes/motor.py (1)
  • update_motor (56-68)
tests/unit/test_routes/conftest.py (1)
  • stub_motor_dump (19-30)
🔇 Additional comments (6)
src/repositories/rocket.py (2)

22-22: Insert payload: LGTM

Using model_dump(exclude_none=True) on create avoids persisting nulls for optional fields and aligns with the new global serialization strategy.


30-32: Re-verify null-field clearing behavior in update_by_id

I ran the suggested search for any tests sending None/null in update payloads and found no matches. However, absence of tests isn’t evidence that no clients rely on clearing fields via null. Please confirm whether any API consumers or tests expect setting a field to null to clear it. If so, address one of the following:

  • Keep exclude_none=True and update your API docs/tests to note that null can no longer clear fields.
  • Restore clearing behavior by splitting the payload into $set and $unset (example below).

File: src/repositories/rocket.py
Lines: 30–32

-        await self.update_by_id(
-            rocket.model_dump(exclude_none=True), data_id=rocket_id
-        )
+        raw = rocket.model_dump()
+        to_unset = {k: "" for k, v in raw.items() if v is None}
+        to_set   = {k: v  for k, v in raw.items() if v is not None}
+        await self.update_by_id(
+            {"$set": to_set, "$unset": to_unset}, data_id=rocket_id
+        )
src/repositories/flight.py (1)

22-22: Insert payload: consistent and correct

exclude_none=True on create is consistent with the new model/view serialization. Good.

src/repositories/motor.py (1)

22-22: Create flow: LGTM

exclude_none=True reduces stored document size and matches the PR-wide policy.

src/views/interface.py (1)

1-1: No JSON null expectations in view responses — approving use of ConfigDict

The only None assertion found (tests/unit/test_repositories/test_repository_interface.py:235) pertains to the repository stub’s return value, not to JSON payloads. There are no tests checking for ": null" in view output, so omitting null fields via ser_json_exclude_none=True is safe.

src/models/interface.py (1)

24-24: BaseModel JSON now omits None globally

The ser_json_exclude_none=True flag on your BaseModel (src/models/interface.py:24) means that any None‐valued fields will be dropped from the JSON output (model_dump_json()/.json()), matching patterns in repos/views.

  • I ran a search for any code or tests that inspect explicit null/None values in JSON serialization and found none.
  • Existing calls using exclude_none=True on .dict() remain unaffected.

Action required:

  • Update your API or developer documentation to note that fields with None values will no longer appear in serialized JSON (clients expecting explicit null entries should be informed).

fix_required

PlainSerializer(to_python_primitive),
]
try:
json_str = json.dumps(
Copy link
Contributor

Choose a reason for hiding this comment

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

@phmbressan @GabrielBarberini not sure who this is for but when i run the env class simulation I get an incorrect date format here

"date": [2025, 6, 9, 23]

get_instance_attributes parses it as a natural datetime object. Is this a encoder issue? Observed this behavior while printing this json_str variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it seems an issue within RocketPyEncoder, I've solved it with a temporary post-processing hack here: 8703230

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: 6

🧹 Nitpick comments (6)
src/views/environment.py (1)

23-39: Clarify “always present” vs Optional typing

The comment says “always present” but fields are Optional[...] with None defaults. Either:

  • make truly required (remove Optional and defaults), or
  • reword to “commonly present” to match the schema.
src/views/rocket.py (1)

33-34: Specify element types for collections

Parachutes are currently typed as plain list; consider at least list[Any] or a dedicated Parachute view model for better docs and validation.

-    parachutes: Optional[list] = None
+    parachutes: Optional[list[Any]] = None
src/views/motor.py (1)

26-26: Refine dry_inertia type for clarity and tooling

Use explicit float tuples to improve schema/docs and static analysis.

-    dry_inertia: Optional[tuple] = None
+    dry_inertia: Optional[tuple[float, float, float]] = None
src/views/flight.py (3)

9-19: Multiple inheritance of Pydantic models: verify MRO and field conflicts

FlightSimulation inherits from both RocketSimulation and EnvironmentSimulation. In Pydantic v2, multiple inheritance can lead to field shadowing (e.g., latitude/longitude) and config conflicts. Ensure this composes as intended and no fields silently override each other.

If issues surface, prefer composition:

  • class FlightSimulation(ApiBaseView) with fields rocket: RocketSimulation and environment: EnvironmentSimulation, plus flight-specific fields.

34-41: Shadowing environment’s latitude/longitude with flight trajectories

These names now represent time-series/derived values (Any), shadowing EnvironmentSimulation’s scalar floats. That’s easy to misinterpret in docs and clients.

Options:

  • Rename to latitude_trajectory/longitude_trajectory/altitude_trajectory, or
  • Keep names but add clear docstrings on these fields indicating they are trajectories, not environment site coordinates.

30-32: Be explicit about initial_solution element types

initial_solution is an opaque list; specify element types to improve docs and correctness.

-    initial_solution: Optional[list] = None
+    initial_solution: Optional[list[float]] = None

If this can be multi-dimensional, consider list[list[float]] or a TypedDict/NamedTuple for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d1f9a97 and 1ff3f2d.

📒 Files selected for processing (4)
  • src/views/environment.py (2 hunks)
  • src/views/flight.py (1 hunks)
  • src/views/motor.py (1 hunks)
  • src/views/rocket.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-15T15:12:21.314Z
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.

Applied to files:

  • src/views/motor.py
🧬 Code graph analysis (4)
src/views/rocket.py (4)
src/services/rocket.py (6)
  • rocket (96-97)
  • rocket (100-101)
  • check_parachute_trigger (220-235)
  • get_rocket_simulation (103-112)
  • RocketService (23-233)
  • from_rocket_model (30-93)
src/models/rocket.py (2)
  • RocketModel (13-63)
  • UPDATED (41-42)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/views/motor.py (2)
  • MotorView (74-75)
  • MotorSimulation (7-71)
src/views/environment.py (2)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/services/environment.py (5)
  • environment (38-39)
  • environment (42-43)
  • EnvironmentService (11-68)
  • from_env_model (18-39)
  • __init__ (14-15)
src/views/flight.py (4)
src/services/flight.py (3)
  • flight (44-45)
  • flight (48-49)
  • FlightService (14-69)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/views/rocket.py (1)
  • RocketSimulation (8-54)
src/views/environment.py (1)
  • EnvironmentSimulation (8-67)
src/views/motor.py (3)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/services/motor.py (2)
  • motor (129-130)
  • motor (133-134)
src/models/motor.py (1)
  • MotorModel (16-95)
🔇 Additional comments (2)
src/views/environment.py (1)

13-17: All services now use rocketpy_encoder as documented

I confirmed that:

  • No instances of get_instance_attributes(...) remain in the codebase.
  • Each service (rocket.py, motor.py, flight.py, environment.py) invokes rocketpy_encoder with the corresponding DiscretizeConfig.for_<service>() preset.
  • The rocketpy_encoder definition in src/utils.py is present and correctly referenced by all services.

No further action is required here.

src/views/motor.py (1)

39-39: Confirm property naming: interpolate vs interpolation_method

MotorModel uses interpolation_method with a fixed set of literals. If rocketpy_encoder outputs interpolation_method (or a literal), align the name to avoid confusion. If RocketPy’s runtime attribute is actually interpolate, keep it but document the divergence.

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 (1)
src/utils.py (1)

92-103: Fix pipeline failure: remove unused json_str and the redundant JSON round-trip.

Pylint flags json_str as unused, and the JSON round-trip is unnecessary since you already return encoder.default(obj_copy).

Apply this diff:

-        json_str = json.dumps(
-            obj_copy,
-            cls=RocketPyEncoder,
-            include_outputs=True,
-            include_function_data=True,
-        )
-        encoder = RocketPyEncoder(
+        encoder = RocketPyEncoder(
             include_outputs=True,
             include_function_data=True,
         )
         return encoder.default(obj_copy)
🧹 Nitpick comments (6)
src/utils.py (6)

7-7: Nit: Prefer built-in generics over typing.Tuple if on Python ≥3.9.

If the project targets Python 3.9+, consider using tuple[float, float] in type hints and dropping Tuple from imports. If older versions are supported, ignore this.

What Python versions does the project officially support (pyproject/poetry.lock/README)? If it's 3.9+, we can modernize hints repo-wide.


17-30: Validate DiscretizeConfig inputs (bounds order and positive samples).

Add lightweight guards so misconfiguration fails fast.

Apply this diff to enforce invariants:

 class DiscretizeConfig:
@@
-    def __init__(
-        self, bounds: Tuple[float, float] = (0, 10), samples: int = 200
-    ):
-        self.bounds = bounds
-        self.samples = samples
+    def __init__(
+        self, bounds: Tuple[float, float] = (0, 10), samples: int = 200
+    ):
+        if samples <= 0:
+            raise ValueError("samples must be a positive integer")
+        if bounds[0] >= bounds[1]:
+            raise ValueError("bounds must be an increasing (lower, upper) tuple")
+        self.bounds = bounds
+        self.samples = samples

48-48: Avoid a mutable default for config.

Use a None default and instantiate inside to prevent accidental sharing if the object later grows mutable fields.

Apply this diff:

-from typing import NoReturn, Tuple
+from typing import NoReturn, Tuple, Optional
@@
-def rocketpy_encoder(obj, config: DiscretizeConfig = DiscretizeConfig()):
+def rocketpy_encoder(obj, config: Optional[DiscretizeConfig] = None):
@@
-    # Create a copy to avoid mutating the original object
-    obj_copy = copy.deepcopy(obj)
+    # Create a copy to avoid mutating the original object
+    cfg = config or DiscretizeConfig()
+    obj_copy = copy.deepcopy(obj)

And replace later config. usages with cfg..


64-66: Performance: deep-copying full RocketPy objects can be heavy.

Consider a copy strategy flag (e.g., "deep" | "shallow" | "none") defaulting to "deep", so power users can skip or shallow-copy in performance-sensitive paths.

If you’re open to it, I can follow up with a minimal copy_strategy param + docs.


76-87: Preserve Function metadata when discretizing.

Reconstructing with Function(attr_value.source) risks losing metadata (units, interpolation settings, cache, etc.). Prefer cloning the original function and discretizing the clone.

Apply this diff:

-        if callable(attr_value) and isinstance(attr_value, Function):
+        if callable(attr_value) and isinstance(attr_value, Function):
             try:
-                discretized_func = Function(attr_value.source)
-                discretized_func.set_discrete(
+                # Clone original to preserve metadata, then discretize the clone
+                discretized_func = copy.deepcopy(attr_value)
+                discretized_func.set_discrete(
-                    lower=config.bounds[0],
-                    upper=config.bounds[1],
-                    samples=config.samples,
+                    lower=cfg.bounds[0],
+                    upper=cfg.bounds[1],
+                    samples=cfg.samples,
                     mutate_self=True,
                 )
 
                 setattr(obj_copy, attr_name, discretized_func)

If RocketPy provides an official clone/copy API for Function, use that instead of deepcopy.


104-114: Fallback path currently coerces all values to strings, losing numeric/boolean types.

Preserve JSON-native primitives and only stringify non-serializable values to avoid breaking clients.

Apply this diff:

-        attributes = {}
-        for attr_name in dir(obj_copy):
-            if not attr_name.startswith('_'):
-                try:
-                    attr_value = getattr(obj_copy, attr_name)
-                    if not callable(attr_value):
-                        attributes[attr_name] = str(attr_value)
-                except Exception:
-                    continue
-        return attributes
+        def _to_json_safe(v):
+            from datetime import date, datetime
+            from decimal import Decimal
+            if isinstance(v, (str, int, float, bool)) or v is None:
+                return v
+            if isinstance(v, (date, datetime)):
+                return v.isoformat()
+            if isinstance(v, Decimal):
+                return float(v)
+            if isinstance(v, (list, tuple)):
+                return [_to_json_safe(i) for i in v]
+            if isinstance(v, dict):
+                return {str(k): _to_json_safe(val) for k, val in v.items()}
+            return str(v)
+
+        attributes: dict = {}
+        for attr_name in dir(obj_copy):
+            if attr_name.startswith('_'):
+                continue
+            try:
+                attr_value = getattr(obj_copy, attr_name)
+            except Exception:
+                continue
+            if callable(attr_value):
+                continue
+            attributes[attr_name] = _to_json_safe(attr_value)
+        return attributes

This keeps numbers/bools as numbers/bools and only stringifies unknowns.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff3f2d and 6342c68.

📒 Files selected for processing (1)
  • src/utils.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
src/utils.py

92-92: Local variable json_str is assigned to but never used

(F841)

🪛 GitHub Actions: Pylint
src/utils.py

[error] 92-92: Pylint: Unused variable 'json_str'. (unused-variable). Command: pylint -d C0415,C0200,C0301,C0114,R0903,C0115,W0246,R0914,C0209,E1121,C0103,C2801,R0801,E1101,E0401,E0611,R0911,C0116,W0212,W0719,W0601,W1203,W0123,W0511,W0621,R0913,R0917 $(git ls-files '*.py')

🔇 Additional comments (5)
src/utils.py (5)

3-5: LGTM on the new imports.

logging, json, and copy are all used below. No issues here.


9-10: Avoid relying on a private module path (rocketpy._encoders).

Leading underscore indicates an internal API that may change without notice. Prefer a public import path if RocketPy exposes one, or pin RocketPy to a version that guarantees _encoders.RocketPyEncoder exists.

Would you check RocketPy’s docs/changelog to confirm if RocketPyEncoder is publicly exported (and from which module) in the version you pin in this repo?


14-14: LGTM on module-level logger.

Good practice for controlled logging across utils.


31-46: Confirm domain defaults and units for discretization presets.

  • Environment: (0, 50000) — meters? altitude?
  • Motor: (0, 10) — seconds of burn time?
  • Rocket: (0, 1) — nondimensional?
  • Flight: (0, 30) — seconds after launch?

If these represent time/altitude, ensure they match RocketPy function domains to avoid under/over-sampling artifacts.

Do these presets align with typical ranges we simulate across services (Environment/Motor/Rocket/Flight)? If needed, I can add docstrings per preset to state units and rationale.


142-142: License/attribution check for the forked Starlette GZip code.

The comment references a Starlette source file. Ensure BSD-3-Clause attribution requirements are met (license notice retained in this file or centrally in a THIRD_PARTY or LICENSES manifest).

If not already present, add appropriate attribution for Starlette’s gzip middleware fork.

GabrielBarberini and others added 6 commits August 24, 2025 13:20
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>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

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

198-274: Fix pipeline failure (pylint R0915: too-many-statements) by refactoring send_with_gzip into small handlers. Also, improve Content-Type check.

  • Current method has 55 statements; GH Actions is failing. Split into handlers to reduce complexity.
  • Replace the raw header bytes check with a proper Content-Type lookup via Headers.

Apply this diff to slim the dispatcher:

 async def send_with_gzip(self, message: Message) -> None:
-        message_type = message["type"]
-        if message_type == "http.response.start":
-            # Don't send the initial message until we've determined how to
-            # modify the outgoing headers correctly.
-            self.initial_message = message
-            headers = Headers(raw=self.initial_message["headers"])
-            self.content_encoding_set = "content-encoding" in headers
-        elif (
-            message_type == "http.response.body" and self.content_encoding_set
-        ):
-            if not self.started:
-                self.started = True
-                await self.send(self.initial_message)
-            await self.send(message)
-        elif message_type == "http.response.body" and not self.started:
-            self.started = True
-            body = message.get("body", b"")
-            more_body = message.get("more_body", False)
-            if ((len(body) < self.minimum_size) and not more_body) or any(
-                value == b'application/octet-stream'
-                for header, value in self.initial_message["headers"]
-            ):
-                # Don't apply GZip to small outgoing responses or octet-streams.
-                await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
-            elif not more_body:
-                # Standard GZip response.
-                self.gzip_file.write(body)
-                self.gzip_file.close()
-                body = self.gzip_buffer.getvalue()
-
-                headers = MutableHeaders(raw=self.initial_message["headers"])
-                headers["Content-Encoding"] = "gzip"
-                headers["Content-Length"] = str(len(body))
-                headers.add_vary_header("Accept-Encoding")
-                message["body"] = body
-
-                await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
-            else:
-                # Initial body in streaming GZip response.
-                headers = MutableHeaders(raw=self.initial_message["headers"])
-                headers["Content-Encoding"] = "gzip"
-                headers.add_vary_header("Accept-Encoding")
-                del headers["Content-Length"]
-
-                self.gzip_file.write(body)
-                message["body"] = self.gzip_buffer.getvalue()
-                self.gzip_buffer.seek(0)
-                self.gzip_buffer.truncate()
-
-                await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
-
-        elif message_type == "http.response.body":
-            # Remaining body in streaming GZip response.
-            body = message.get("body", b"")
-            more_body = message.get("more_body", False)
-
-            self.gzip_file.write(body)
-            if not more_body:
-                self.gzip_file.close()
-
-            message["body"] = self.gzip_buffer.getvalue()
-            self.gzip_buffer.seek(0)
-            self.gzip_buffer.truncate()
-
-            await self.send(message)
-
-        else:
-            # Pass through other message types unmodified.
-            if not self.started:
-                self.started = True
-                await self.send(self.initial_message)
-            await self.send(message)
+        message_type = message["type"]
+        if message_type == "http.response.start":
+            await self._on_response_start(message)
+        elif message_type == "http.response.body":
+            await self._on_response_body(message)
+        else:
+            await self._pass_through(message)

Add these helper methods inside GZipResponder (below send_with_gzip):

    async def _on_response_start(self, message: Message) -> None:
        # Defer sending headers until we know if we will compress
        self.initial_message = message
        headers = Headers(raw=self.initial_message["headers"])
        self.content_encoding_set = "content-encoding" in headers

    async def _on_response_body(self, message: Message) -> None:
        if self.content_encoding_set:
            if not self.started:
                self.started = True
                await self.send(self.initial_message)
            await self.send(message)
            return

        if not self.started:
            self.started = True
            body = message.get("body", b"")
            more_body = message.get("more_body", False)

            # Skip gzip for small bodies and for octet-streams (and other already-compressed types).
            init_headers = Headers(raw=self.initial_message["headers"])
            content_type = (init_headers.get("content-type") or "").lower()
            is_uncompressible = (
                content_type.startswith("application/octet-stream")
                or content_type.startswith("image/")
                or content_type.startswith("video/")
            )
            if ((len(body) < self.minimum_size) and not more_body) or is_uncompressible:
                await self.send(self.initial_message)
                await self.send(message)
                return

            if not more_body:
                # Standard non-streaming gzip
                self.gzip_file.write(body)
                self.gzip_file.close()
                compressed = self.gzip_buffer.getvalue()

                headers = MutableHeaders(raw=self.initial_message["headers"])
                headers["Content-Encoding"] = "gzip"
                headers["Content-Length"] = str(len(compressed))
                headers.add_vary_header("Accept-Encoding")
                message["body"] = compressed

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

            # Streaming gzip: first chunk
            headers = MutableHeaders(raw=self.initial_message["headers"])
            headers["Content-Encoding"] = "gzip"
            headers.add_vary_header("Accept-Encoding")
            if "Content-Length" in headers:
                del headers["Content-Length"]

            self.gzip_file.write(body)
            message["body"] = self.gzip_buffer.getvalue()
            self.gzip_buffer.seek(0)
            self.gzip_buffer.truncate()

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

        # Streaming gzip: subsequent chunks
        body = message.get("body", b"")
        more_body = message.get("more_body", False)

        self.gzip_file.write(body)
        if not more_body:
            self.gzip_file.close()

        message["body"] = self.gzip_buffer.getvalue()
        self.gzip_buffer.seek(0)
        self.gzip_buffer.truncate()

        await self.send(message)

    async def _pass_through(self, message: Message) -> None:
        # Pass through other message types unmodified.
        if not self.started:
            self.started = True
            await self.send(self.initial_message)
        await self.send(message)

This should satisfy pylint and make the control flow easier to reason about.

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

18-31: Consider using a dataclass and validate inputs in DiscretizeConfig.

Current class is fine, but a dataclass reduces boilerplate and explicit validation prevents silent misconfigurations (negative samples, inverted bounds).

Example diff for minimal validation (keeps current API):

 class DiscretizeConfig:
@@
-    def __init__(
-        self, bounds: Tuple[float, float] = (0, 10), samples: int = 200
-    ):
-        self.bounds = bounds
-        self.samples = samples
+    def __init__(
+        self, bounds: Tuple[float, float] = (0, 10), samples: int = 200
+    ):
+        lower, upper = bounds
+        if lower >= upper:
+            raise ValueError(f"bounds must be (lower < upper), got {bounds}")
+        if samples <= 0:
+            raise ValueError(f"samples must be > 0, got {samples}")
+        self.bounds = (float(lower), float(upper))
+        self.samples = int(samples)

32-47: Preset bounds look domain-specific—please verify they match intended usage across Environment/Motor/Rocket/Flight.

  • These presets will drive discretization resolution and can affect performance and accuracy.
  • If they’re empirically chosen, consider documenting the rationale next to each preset.

Would you like a quick benchmark harness to compare encode time/size vs. samples for typical objects?


77-87: Preserve Function metadata and skip already-discrete functions.

Reconstructing with Function(attr_value.source) may drop metadata (name, interpolation, units). Prefer deepcopy; also skip if is_discrete is already true.

-        if callable(attr_value) and isinstance(attr_value, Function):
+        if isinstance(attr_value, Function):
             try:
-                discretized_func = Function(attr_value.source)
-                discretized_func.set_discrete(
+                # Skip functions already discretized
+                if getattr(attr_value, "is_discrete", False):
+                    continue
+                # Preserve metadata
+                discretized_func = copy.deepcopy(attr_value)
+                discretized_func.set_discrete(
                     lower=config.bounds[0],
                     upper=config.bounds[1],
                     samples=config.samples,
                     mutate_self=True,
                 )
 
                 setattr(obj_copy, attr_name, discretized_func)

92-103: Nice: deep-copy + official RocketPyEncoder + post-processing.

This should address prior mutation concerns and the earlier “date encoded as list” report. The JSON round-trip is okay for robustness, but if RocketPy provides a direct dict encoder, consider using that to avoid an extra serialize/parse.


104-114: Fallback loses types by coercing to str; preserve JSON-serializable structure when possible.

Keep numbers/lists/dicts intact and only stringify non-serializable leaves.

-                        attributes[attr_name] = str(attr_value)
+                        try:
+                            # Preserve JSON-serializable types; stringify unknowns
+                            attributes[attr_name] = json.loads(
+                                json.dumps(attr_value, default=str)
+                            )
+                        except Exception:
+                            attributes[attr_name] = str(attr_value)

117-149: Type hints and edge cases in _fix_datetime_fields.

  • Add type hints.
  • Consider timezone support and month/day bounds if the encoder ever emits tz info or invalid lists. Current try/except is safe; documenting assumptions helps.
-def _fix_datetime_fields(data):
+def _fix_datetime_fields(data: Any) -> Any:

If tz-awareness becomes necessary later, we can extend this to detect an 8th/9th element for offset and attach a timezone.


217-221: Use Headers API for Content-Type instead of raw tuple scan.

Raw header values may include parameters (e.g., charset) and casing varies. Use Headers(...).get("content-type") and a prefix check. The larger refactor above includes this.


176-176: Attribution comment is helpful; consider pinning Starlette version assumptions.

Since this is a fork of Starlette gzip, consider noting the Starlette version you mirrored to ease future diffs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6342c68 and 8703230.

📒 Files selected for processing (1)
  • src/utils.py (3 hunks)
🧰 Additional context used
🪛 GitHub Actions: Pylint
src/utils.py

[error] 198-198: pylint: R0915 Too many statements (55/50) (too-many-statements)

🔇 Additional comments (4)
src/utils.py (4)

151-174: Middleware defaults LGTM.

Sensible defaults for minimum_size and compresslevel, and clean Accept-Encoding gating.


65-65: Kudos for addressing the earlier mutation concern.

Deep-copying the object prior to discretization aligns with prior review feedback and avoids shared-state bugs.


101-103: Confirm downstream expects datetime objects post-fix.

_fix_datetime_fields returns Python datetime objects. If responses are later serialized by FastAPI/Pydantic, that’s fine (ISO strings). If they are consumed as plain dicts elsewhere, ensure those consumers handle datetime correctly.


1-114: No deprecated utilities or stray encoding calls detected
Repository-wide verification confirms:

  • No references to get_instance_attributes, to_python_primitive, or AnyToPrimitive.
  • No direct json.dumps(..., cls=RocketPyEncoder) invocations.
  • No ad-hoc setattr(..., Function(...)) patterns outside rocketpy_encoder.

The new rocketpy_encoder fully consolidates serialization logic. No further action required.

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: 2

♻️ Duplicate comments (4)
src/views/motor.py (1)

18-22: Good: preserves None-omission and enables dynamic attrs

Including ser_json_exclude_none=True while allowing extras and arbitrary types keeps responses clean and supports encoder-driven fields. Matches the pattern used across views.

src/views/environment.py (2)

19-23: Good: merges parent behavior and supports dynamic attrs

Keeping ser_json_exclude_none=True avoids null noise; extra='allow' and arbitrary_types_allowed=True are appropriate for encoder output.


44-46: Fix timezone-naive, import-time datetime defaults

Defaults evaluated at import time and naive datetimes lead to non-deterministic behavior across workers/timezones. Use Field(default_factory=...) with timezone-aware now().

Apply this diff:

-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
-from pydantic import ConfigDict
+from pydantic import ConfigDict, Field
@@
-    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] = Field(default_factory=lambda: datetime.now(timezone.utc) + timedelta(days=1))
+    local_date: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc) + timedelta(days=1))
+    datetime_date: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc) + timedelta(days=1))
src/views/rocket.py (1)

21-25: Good: consistent model_config with None-omission

Matches other simulations; keeps responses lean while allowing dynamic fields.

🧹 Nitpick comments (9)
src/views/motor.py (3)

26-26: Nit: “always present” is misleading

These “core” fields are Optional and may be omitted by the encoder; consider rewording to “commonly present” to avoid confusing API consumers.


30-30: Tighten type for dry_inertia to match model and docs

MotorModel defines dry_inertia as a 3-tuple of floats. Using plain tuple loses structure and harms schema quality.

Apply this diff:

-    dry_inertia: Optional[tuple] = None
+    dry_inertia: Optional[tuple[float, float, float]] = None

48-75: Optional: introduce a shared alias for encoded series

Many of these fields represent discretized series/curves. Define a reusable type alias (e.g., EncodedSeries = list[float] | list[tuple[float, float]] | dict[str, Any]) to improve generated docs and client expectations.

I can draft a small EncodedSeries alias and apply it across views if you want.

src/views/environment.py (3)

30-30: Default elevation=1 is surprising; prefer None or project default

EnvironmentModel uses 0.0. Consider aligning or using None to omit it from JSON when not provided.

Apply this diff:

-    elevation: Optional[float] = 1
+    elevation: Optional[float] = None

43-43: Type of max_expected_height likely needs float

Heights are usually non-integer; int restricts precision.

Apply this diff:

-    max_expected_height: Optional[int] = None
+    max_expected_height: Optional[float] = None

48-71: Optional: shared alias for encoded curves/fields

Similar to Motor/Rocket, these Optional[Any] series could benefit from a shared EncodedSeries alias to improve generated API docs without over-constraining shapes.

Happy to prepare the alias and update these fields if you want.

src/views/rocket.py (3)

42-60: Optional: consolidate function-attribute typing via alias

Same suggestion as Motor/Environment: define EncodedSeries to describe these discretized outputs, improving OpenAPI docs while keeping flexibility.

I can introduce EncodedSeries in a shared module and update these fields in one PR commit.


37-37: Unify center_of_mass_without_motor type between model and view

The RocketModel.center_of_mass_without_motor is currently declared as an int (src/models/rocket.py:22), whereas in the view it’s Optional[float] (src/views/rocket.py:37). This inconsistency may lead to confusion for API consumers and code generators.

Choose one of the following refactoring options:

  • Prefer float everywhere (more physically accurate):
    • Update the model declaration to float (or Optional[float] if None is valid).
    • Ensure the view uses the same signature.
  • Or match the model’s current int type for consistency:
    • Change the view to Optional[int].

Locations to update:

  • src/models/rocket.py (line 22)
  • src/views/rocket.py (line 37)

Example diffs for each approach:

  1. Floating-point everywhere
--- src/models/rocket.py
-    center_of_mass_without_motor: int
+    center_of_mass_without_motor: float

--- src/views/rocket.py
-    center_of_mass_without_motor: Optional[float] = None
+    center_of_mass_without_motor: float
  1. Integer everywhere
--- src/views/rocket.py
-    center_of_mass_without_motor: Optional[float] = None
+    center_of_mass_without_motor: Optional[int] = None

Please update both definitions to the chosen type so that clients and code generators see a consistent schema.


39-41: Refine the parachutes field to use the existing Parachute model

The parachutes: Optional[list] = None declaration in src/views/rocket.py (around lines 39–41) can be tightened to leverage the Pydantic model you already have.

• File: src/views/rocket.py, lines 39–41
Change:

-   parachutes: Optional[list] = None
+   from typing import List
+   from src.models.sub.aerosurfaces import Parachute
+
+   parachutes: Optional[List[Parachute]] = None

This ensures each parachute entry conforms to your domain schema.

• The motor: Optional[MotorSimulation] = None field is already typed appropriately; just verify that your encoder’s output aligns with the nested MotorSimulation structure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8703230 and a26e01c.

📒 Files selected for processing (4)
  • src/views/environment.py (2 hunks)
  • src/views/flight.py (1 hunks)
  • src/views/motor.py (1 hunks)
  • src/views/rocket.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/views/flight.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-15T15:12:21.314Z
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.

Applied to files:

  • src/views/motor.py
🧬 Code graph analysis (3)
src/views/environment.py (4)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/services/environment.py (5)
  • environment (38-39)
  • environment (42-43)
  • EnvironmentService (11-68)
  • from_env_model (18-39)
  • __init__ (14-15)
src/models/environment.py (1)
  • EnvironmentModel (6-50)
src/controllers/environment.py (1)
  • EnvironmentController (10-61)
src/views/rocket.py (5)
src/services/rocket.py (3)
  • rocket (96-97)
  • rocket (100-101)
  • get_rocket_simulation (103-112)
src/models/rocket.py (2)
  • RocketModel (13-63)
  • DELETED (45-46)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/views/motor.py (2)
  • MotorView (78-79)
  • MotorSimulation (7-75)
tests/unit/test_routes/test_rockets_route.py (1)
  • stub_rocket_simulation_dump (24-27)
src/views/motor.py (3)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/services/motor.py (4)
  • motor (129-130)
  • motor (133-134)
  • MotorService (23-154)
  • get_motor_simulation (136-145)
src/models/motor.py (1)
  • MotorModel (16-95)
🔇 Additional comments (2)
src/views/rocket.py (2)

30-37: Nice: tightened types for inertia and drag curves

Precise tuple and list[tuple[float,float]] types materially improve schema/doc quality.


8-19: No remaining references to get_instance_attributes found

The ripgrep search (rg -n -C2 -g '!**/dist/**' -g '!**/build/**' '\bget_instance_attributes\s*\(') returned no matches, confirming that all calls to get_instance_attributes have been removed repo-wide and replaced with the new rocketpy_encoder. No further action is required here.

Copy link
Contributor

@aasitvora99 aasitvora99 left a comment

Choose a reason for hiding this comment

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

send it 🚀

@GabrielBarberini GabrielBarberini merged commit 5504668 into master Aug 24, 2025
4 checks passed
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: 2

♻️ Duplicate comments (5)
src/utils.py (2)

83-93: Copying Function via source may drop metadata; prefer deepcopy or guarded fallback

Creating a new Function from .source can lose configured state (units, interpolation, metadata). Use deepcopy when possible, or guard when source is absent.

Apply this diff:

-        if callable(attr_value) and isinstance(attr_value, Function):
+        if callable(attr_value) and isinstance(attr_value, Function):
             try:
-                discretized_func = Function(attr_value.source)
+                # Try to preserve full state first
+                try:
+                    discretized_func = copy.deepcopy(attr_value)
+                except Exception:
+                    # Fallback: rebuild from source if available
+                    if hasattr(attr_value, "source") and attr_value.source is not None:
+                        discretized_func = Function(attr_value.source)
+                    else:
+                        raise
                 discretized_func.set_discrete(
                     lower=config.bounds[0],
                     upper=config.bounds[1],
                     samples=config.samples,
                     mutate_self=True,
                 )

8-8: Avoid constructed default argument; use Optional[DiscretizeConfig] = None

The function already normalizes None to DiscretizeConfig(), but the signature still constructs a default instance at import time. This can lead to subtle shared-state bugs and contradicts the intent. Fix the signature and import Optional.

Apply this diff:

-from typing import NoReturn, Tuple
+from typing import NoReturn, Tuple, Optional
@@
-def rocketpy_encoder(obj, config: DiscretizeConfig = DiscretizeConfig()):
+def rocketpy_encoder(obj, config: Optional[DiscretizeConfig] = None):
@@
-    if config is None:
-        config = DiscretizeConfig()
+    if config is None:
+        config = DiscretizeConfig()

Run to ensure no callers rely on positional default:

#!/bin/bash
rg -n "rocketpy_encoder\(" -C2

Also applies to: 49-67

src/views/motor.py (3)

28-28: dry_inertia typing is too generic; specify element types

Use a concrete 3-tuple of floats to match semantics and aid validation.

-    dry_inertia: Optional[tuple] = None
+    dry_inertia: Optional[tuple[float, float, float]] = None

24-36: Make burn_time a scalar core attribute, not a function-like series

RocketPy’s burn_time is a scalar duration. Modeling it as Optional[Any] under “Function attributes” weakens guarantees and can break clients expecting a float.

Apply these diffs:

  1. Add burn_time to the core section:
@@
     # Core Motor attributes (always present)
     burn_start_time: Optional[float] = None
     burn_out_time: Optional[float] = None
+    burn_time: Optional[float] = None
     dry_mass: Optional[float] = None
  1. Remove the function-like variant:
-    burn_time: Optional[Any] = None

If the encoder sometimes emits time series for edge cases, consider a separate field name (e.g., burn_time_series) to avoid overloading semantics.

Also applies to: 55-56


1-2: Rename interpolate → interpolation_method and keep backward compatibility with alias

Everywhere else (models, services, docs) uses interpolation_method. Keeping interpolate here creates drift and brittle client contracts. Recommend renaming and accepting the legacy key via Field(alias='interpolate').

Apply this diff:

-from typing import Optional, Any
-from pydantic import ConfigDict
+from typing import Optional, Any
+from pydantic import ConfigDict, Field
@@
-    interpolate: Optional[str] = None
+    interpolation_method: Optional[str] = Field(
+        default=None,
+        alias="interpolate",
+        description="Interpolation method used for discretization (e.g., 'linear', 'spline')."
+    )

Follow-up: If any downstream code reads .interpolate, update it to .interpolation_method. The alias ensures inbound JSON with "interpolate" still works during transition.

Also applies to: 41-41

🧹 Nitpick comments (11)
src/utils.py (2)

111-120: Fallback path coerces values to strings; preserve JSON-serializable primitives

Casting all non-callables to str can silently change types (e.g., numbers -> strings). Prefer preserving primitives and only repr() when not JSON-serializable.

Apply this diff:

-        attributes = {}
+        attributes = {}
         for attr_name in dir(obj_copy):
             if not attr_name.startswith('_'):
                 try:
                     attr_value = getattr(obj_copy, attr_name)
                     if not callable(attr_value):
-                        attributes[attr_name] = str(attr_value)
+                        try:
+                            # Preserve as-is if JSON-serializable
+                            json.dumps(attr_value)
+                            attributes[attr_name] = attr_value
+                        except TypeError:
+                            attributes[attr_name] = repr(attr_value)
                 except Exception:
                     continue
         return attributes

99-106: Avoid JSON round-trip if the encoder exposes a dict interface

If RocketPyEncoder supports returning a dict (e.g., via a .default method or similar), bypassing dumps/loads will reduce overhead and avoid intermediate type coercions.

Would you like me to check the latest RocketPyEncoder API and propose a concrete change?

src/views/environment.py (3)

5-5: Deduplicate datetime factory by importing the shared helper from models

This file redefines _default_future_datetime already provided in src/models/environment.py. Importing the shared helper avoids drift.

Apply this diff:

-from src.models.environment import EnvironmentModel
+from src.models.environment import EnvironmentModel, _default_future_datetime
@@
-def _default_future_datetime() -> datetime:
-    """Factory function to create timezone-aware datetime one day in the future."""
-    return datetime.now(timezone.utc) + timedelta(days=1)
+# Use the shared factory imported from models to ensure consistent behavior

Also applies to: 8-10


30-47: Align elevation default with model or make it explicit why it differs

EnvironmentModel uses elevation: Optional[float] = 0.0, while EnvironmentSimulation sets elevation: Optional[float] = 1. This inconsistency may confuse clients and tests.

Apply this diff if 0.0 is the intended default:

-    elevation: Optional[float] = 1
+    elevation: Optional[float] = 0.0

If 1 meter is intentional for simulations, add a brief comment explaining the rationale.


55-78: Very broad Any types for function attributes; consider tightening where known

Where RocketPy’s encoder shape is stable (e.g., sequences of [x, y] pairs), tightening to concrete types (like list[tuple[float, float]]) improves validation and docs. Keep Any for truly dynamic fields.

If you share a few sample payloads, I can propose precise typings for the most common fields.

src/views/flight.py (2)

9-19: Be cautious with multiple inheritance of Pydantic models

Combining RocketSimulation and EnvironmentSimulation inflates the schema and can create field collisions/MRO surprises. Composition (nested models) or a common base may be easier to reason about and version.

I can sketch a composed FlightSimulation with namespaced rocket/environment submodels if you want to explore that path.


32-35: Type initial_solution concretely

If this represents an array of floats (typical for ODE initial conditions), specify list[float] for stronger validation.

Apply this diff:

-    initial_solution: Optional[list] = None
+    initial_solution: Optional[list[float]] = None
src/views/motor.py (4)

12-16: Comment consistency: “rocketpy_encoder” vs “RocketPy encoders”

Minor naming drift in comments: you refer to the encoder as rocketpy_encoder in one place and RocketPyEncoder elsewhere. Pick one convention (the PR title says “official RocketPy encoders”).

@@
-    # discretized by rocketpy_encoder
-    # serialized by RocketPyEncoder
+    # Discretized and serialized by RocketPy encoders

Also applies to: 43-46


39-40: Narrow these to validated enums/literals (optional)

If these values are expected to belong to known sets, tighten typing to catch invalid payloads early and improve API docs.

Example diff (keeps flexibility but adds validation):

-from typing import Optional, Any
+from typing import Optional, Any, Literal
@@
-    coordinate_system_orientation: Optional[str] = None
-    motor_kind: Optional[str] = None
+    coordinate_system_orientation: Optional[
+        Literal['nozzle_to_combustion_chamber', 'combustion_chamber_to_nozzle']
+    ] = None
+    motor_kind: Optional[str] = None  # or import and use MotorKinds if encoder emits enum-safe values

If the encoder emits the exact MotorKinds values, you can also:

-from src.models.motor import MotorModel
+from src.models.motor import MotorModel, MotorKinds
@@
-    motor_kind: Optional[str] = None
+    motor_kind: Optional[MotorKinds] = None

46-74: Replace broad Any types for series with a documented type alias (optional)

Most “function attributes” look like discretized series or scalars. Using Any everywhere hurts discoverability and client generation.

Pattern proposal:

-from typing import Optional, Any
+from typing import Optional, Any, Sequence, Tuple, Union, TypeAlias
+
+# Series of (time, value) pairs or a scalar value
+EncodedSeries: TypeAlias = Sequence[Tuple[float, float]]
+EncodedValue: TypeAlias = Union[float, EncodedSeries]
@@
-    burn_area: Optional[Any] = None
-    burn_rate: Optional[Any] = None
+    burn_area: Optional[EncodedValue] = None
+    burn_rate: Optional[EncodedValue] = None
@@
-    thrust: Optional[Any] = None
+    thrust: Optional[EncodedValue] = None

I can push a batch edit to convert the remaining attributes in this block to EncodedValue; say the word and I’ll prepare a follow-up diff.


1-1: Minor import hygiene

With the above suggestions (Field, Literal, type aliases), keep imports consolidated and minimal. No action needed if you accept the diffs as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a26e01c and b574253.

📒 Files selected for processing (7)
  • pyproject.toml (1 hunks)
  • src/models/environment.py (2 hunks)
  • src/utils.py (3 hunks)
  • src/views/environment.py (2 hunks)
  • src/views/flight.py (1 hunks)
  • src/views/motor.py (1 hunks)
  • src/views/rocket.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/views/rocket.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-15T15:12:21.314Z
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.

Applied to files:

  • src/views/motor.py
🧬 Code graph analysis (4)
src/models/environment.py (2)
src/models/interface.py (1)
  • ApiBaseModel (10-61)
src/views/environment.py (1)
  • _default_future_datetime (8-10)
src/views/environment.py (2)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/models/environment.py (2)
  • EnvironmentModel (12-54)
  • _default_future_datetime (7-9)
src/views/motor.py (3)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/services/motor.py (3)
  • motor (129-130)
  • motor (133-134)
  • get_motor_simulation (136-145)
src/models/motor.py (1)
  • MotorModel (16-95)
src/views/flight.py (4)
src/services/flight.py (3)
  • flight (44-45)
  • flight (48-49)
  • FlightService (14-69)
src/models/flight.py (1)
  • FlightModel (7-71)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/views/rocket.py (2)
  • RocketView (62-64)
  • RocketSimulation (8-59)
🔇 Additional comments (5)
src/models/environment.py (1)

7-9: Good move to timezone-aware, per-instance defaults

Switching to Field(default_factory=...) with a UTC-aware factory eliminates import-time evaluation and naive datetimes. This aligns well with ApiBaseModel serialization behavior.

Also applies to: 29-29

src/views/environment.py (1)

24-26: LGTM on model_config

Including ser_json_exclude_none while allowing extras keeps responses clean and future-proof.

src/views/flight.py (1)

21-23: LGTM on model_config

ser_json_exclude_none with extra='allow' is consistent with other views and prevents None spam in responses.

src/views/motor.py (2)

18-20: Model config aligns with repo conventions (exclude None + allow extras) — LGTM

Good job mirroring the other Simulation views by enabling ser_json_exclude_none and extra='allow'. This keeps responses lean and future‑proof for dynamic encoder fields.


7-16: Nice: dynamic-attr friendly Simulation view with extra='allow'

The docstring and extra='allow' approach is a good fit for the new encoder-driven payloads and buys us forward compatibility as RocketPy evolves.

Comment on lines +98 to +109
try:
json_str = json.dumps(
obj_copy,
cls=RocketPyEncoder,
include_outputs=True,
include_function_data=True,
)
encoded_result = json.loads(json_str)

return str(v)
# Post-process to fix datetime fields that got converted to lists
return _fix_datetime_fields(encoded_result)
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Naive datetimes reintroduced by post-processing; make them UTC-aware

_fix_datetime_fields builds naive datetime objects, conflicting with the new UTC-aware defaults elsewhere (e.g., Environment defaults). This inconsistency can break consumers expecting tz-aware timestamps.

Apply this diff:

-from datetime import datetime
+from datetime import datetime, timezone
@@
-                    fixed[key] = datetime(
-                        year, month, day, hour, minute, second, microsecond
-                    )
+                    fixed[key] = datetime(
+                        year, month, day, hour, minute, second, microsecond, tzinfo=timezone.utc
+                    )

Also applies to: 123-155

🤖 Prompt for AI Agents
In src/utils.py around lines 98 to 109 (and also apply same change to lines
123-155), _fix_datetime_fields currently reconstructs datetime objects as naive
datetimes; update the post-processing so any datetime reconstructed from
lists/tuples is made timezone-aware by setting UTC (e.g., use datetime(...,
tzinfo=timezone.utc) or .replace(tzinfo=timezone.utc)) instead of leaving them
naive, ensuring all returned datetimes are UTC-aware and consistent with
Environment defaults; adjust any imports and tests as needed to use
datetime.timezone.utc.

Comment on lines +1 to 74
from typing import Optional, Any
from pydantic import ConfigDict
from src.views.interface import ApiBaseView
from src.models.motor import MotorModel
from src.utils import AnyToPrimitive


class MotorSimulation(BaseModel):
class MotorSimulation(ApiBaseView):
"""
Motor simulation view that handles dynamically
encoded RocketPy Motor attributes.
Uses the new rocketpy_encoder which may return
different attributes based on the actual RocketPy Motor object.
The model allows extra fields to accommodate any
new attributes that might be encoded.
"""

model_config = ConfigDict(
ser_json_exclude_none=True, extra='allow', arbitrary_types_allowed=True
)

message: str = "Motor successfully simulated"
average_thrust: Optional[float] = None
burn_duration: Optional[float] = None
burn_out_time: Optional[float] = None

# Core Motor attributes (always present)
burn_start_time: Optional[float] = None
center_of_dry_mass_position: Optional[float] = None
coordinate_system_orientation: str = 'nozzle_to_combustion_chamber'
dry_I_11: Optional[float] = None
dry_I_12: Optional[float] = None
dry_I_13: Optional[float] = None
dry_I_22: Optional[float] = None
dry_I_23: Optional[float] = None
dry_I_33: Optional[float] = None
burn_out_time: Optional[float] = None
dry_mass: Optional[float] = None
grain_burn_out: Optional[float] = None
grain_density: Optional[float] = None
grain_initial_height: Optional[float] = None
grain_initial_inner_radius: Optional[float] = None
grain_initial_mass: Optional[float] = None
grain_initial_volume: Optional[float] = None
dry_inertia: Optional[tuple] = None
center_of_dry_mass_position: Optional[float] = None
grains_center_of_mass_position: Optional[float] = None
grain_number: Optional[int] = None
grain_density: Optional[float] = None
grain_outer_radius: Optional[float] = None
grain_separation: Optional[float] = None
grains_center_of_mass_position: Optional[float] = None
interpolate: Optional[str] = None
max_thrust: Optional[float] = None
max_thrust_time: Optional[float] = None
nozzle_position: Optional[float] = None
grain_initial_inner_radius: Optional[float] = None
grain_initial_height: Optional[float] = None
nozzle_radius: Optional[float] = None
propellant_initial_mass: Optional[float] = None
throat_area: Optional[float] = None
throat_radius: Optional[float] = None
thrust_source: Optional[List[List[float]]] = None
total_impulse: Optional[float] = None
Kn: Optional[AnyToPrimitive] = None
I_11: Optional[AnyToPrimitive] = None
I_12: Optional[AnyToPrimitive] = None
I_13: Optional[AnyToPrimitive] = None
I_22: Optional[AnyToPrimitive] = None
I_23: Optional[AnyToPrimitive] = None
I_33: Optional[AnyToPrimitive] = None
burn_area: Optional[AnyToPrimitive] = None
burn_rate: Optional[AnyToPrimitive] = None
burn_time: Optional[AnyToPrimitive] = None
center_of_mass: Optional[AnyToPrimitive] = None
center_of_propellant_mass: Optional[AnyToPrimitive] = None
exhaust_velocity: Optional[AnyToPrimitive] = None
grain_height: Optional[AnyToPrimitive] = None
grain_volume: Optional[AnyToPrimitive] = None
grain_inner_radius: Optional[AnyToPrimitive] = None
mass_flow_rate: Optional[AnyToPrimitive] = None
propellant_I_11: Optional[AnyToPrimitive] = None
propellant_I_12: Optional[AnyToPrimitive] = None
propellant_I_13: Optional[AnyToPrimitive] = None
propellant_I_22: Optional[AnyToPrimitive] = None
propellant_I_23: Optional[AnyToPrimitive] = None
propellant_I_33: Optional[AnyToPrimitive] = None
propellant_mass: Optional[AnyToPrimitive] = None
reshape_thrust_curve: Optional[AnyToPrimitive] = None
total_mass: Optional[AnyToPrimitive] = None
total_mass_flow_rate: Optional[AnyToPrimitive] = None
thrust: Optional[AnyToPrimitive] = None
nozzle_position: Optional[float] = None
coordinate_system_orientation: Optional[str] = None
motor_kind: Optional[str] = None
interpolate: Optional[str] = None

# Function attributes
# discretized by rocketpy_encoder
# serialized by RocketPyEncoder
Kn: Optional[Any] = None
I_11: Optional[Any] = None
I_12: Optional[Any] = None
I_13: Optional[Any] = None
I_22: Optional[Any] = None
I_23: Optional[Any] = None
I_33: Optional[Any] = None
burn_area: Optional[Any] = None
burn_rate: Optional[Any] = None
burn_time: Optional[Any] = None
center_of_mass: Optional[Any] = None
center_of_propellant_mass: Optional[Any] = None
exhaust_velocity: Optional[Any] = None
grain_height: Optional[Any] = None
grain_volume: Optional[Any] = None
grain_inner_radius: Optional[Any] = None
mass_flow_rate: Optional[Any] = None
propellant_I_11: Optional[Any] = None
propellant_I_12: Optional[Any] = None
propellant_I_13: Optional[Any] = None
propellant_I_22: Optional[Any] = None
propellant_I_23: Optional[Any] = None
propellant_I_33: Optional[Any] = None
propellant_mass: Optional[Any] = None
reshape_thrust_curve: Optional[Any] = None
total_mass: Optional[Any] = None
total_mass_flow_rate: Optional[Any] = None
thrust: Optional[Any] = None

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Sanity-check the PR objectives across the repo (encoder usage, naming, types)

To ensure we fully swapped get_instance_attributes → rocketpy_encoder and avoided naming/type drift, please run this verification script. Expectation: no remaining get_instance_attributes, uniform interpolation_method, and no function-like burn_time in views.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Remaining usage of get_instance_attributes (should be none):"
rg -n -C1 "\bget_instance_attributes\b" || true

echo
echo "2) Check interpolation naming drift (prefer 'interpolation_method'):"
rg -n -C2 -g '!**/dist/**' -g '!**/build/**' '\binterpolat(e|ion_method)\b' || true

echo
echo "3) Views declaring burn_time as non-scalar (should be scalar float):"
rg -n -C2 -g 'src/views/**' '\bburn_time\s*:\s*Optional\[' || true

echo
echo "4) Grep for MotorSimulation schema dumps to see what clients receive:"
rg -n -C2 'MotorSimulation|get_motor_simulation' || true

Length of output: 11206


Refactor MotorSimulation view fields: rename interpolate to interpolation_method and drop the function-like burn_time

To align src/views/motor.py with the rest of the codebase and avoid API drift, please apply the following changes in the MotorSimulation class:

• Replace the incorrectly named interpolation field:

-    interpolate: Optional[str] = None
+    interpolation_method: Optional[str] = None

• Remove the function-attribute declaration for burn_time (it conflicts with the scalar burn_start_time/burn_out_time API):

-    burn_time: Optional[Any] = None

• After making these updates, verify that no other view subclasses (e.g., in src/views/rocket.py) explicitly declare interpolate or burn_time.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from typing import Optional, Any
from pydantic import ConfigDict
from src.views.interface import ApiBaseView
from src.models.motor import MotorModel
from src.utils import AnyToPrimitive
class MotorSimulation(BaseModel):
class MotorSimulation(ApiBaseView):
"""
Motor simulation view that handles dynamically
encoded RocketPy Motor attributes.
Uses the new rocketpy_encoder which may return
different attributes based on the actual RocketPy Motor object.
The model allows extra fields to accommodate any
new attributes that might be encoded.
"""
model_config = ConfigDict(
ser_json_exclude_none=True, extra='allow', arbitrary_types_allowed=True
)
message: str = "Motor successfully simulated"
average_thrust: Optional[float] = None
burn_duration: Optional[float] = None
burn_out_time: Optional[float] = None
# Core Motor attributes (always present)
burn_start_time: Optional[float] = None
center_of_dry_mass_position: Optional[float] = None
coordinate_system_orientation: str = 'nozzle_to_combustion_chamber'
dry_I_11: Optional[float] = None
dry_I_12: Optional[float] = None
dry_I_13: Optional[float] = None
dry_I_22: Optional[float] = None
dry_I_23: Optional[float] = None
dry_I_33: Optional[float] = None
burn_out_time: Optional[float] = None
dry_mass: Optional[float] = None
grain_burn_out: Optional[float] = None
grain_density: Optional[float] = None
grain_initial_height: Optional[float] = None
grain_initial_inner_radius: Optional[float] = None
grain_initial_mass: Optional[float] = None
grain_initial_volume: Optional[float] = None
dry_inertia: Optional[tuple] = None
center_of_dry_mass_position: Optional[float] = None
grains_center_of_mass_position: Optional[float] = None
grain_number: Optional[int] = None
grain_density: Optional[float] = None
grain_outer_radius: Optional[float] = None
grain_separation: Optional[float] = None
grains_center_of_mass_position: Optional[float] = None
interpolate: Optional[str] = None
max_thrust: Optional[float] = None
max_thrust_time: Optional[float] = None
nozzle_position: Optional[float] = None
grain_initial_inner_radius: Optional[float] = None
grain_initial_height: Optional[float] = None
nozzle_radius: Optional[float] = None
propellant_initial_mass: Optional[float] = None
throat_area: Optional[float] = None
throat_radius: Optional[float] = None
thrust_source: Optional[List[List[float]]] = None
total_impulse: Optional[float] = None
Kn: Optional[AnyToPrimitive] = None
I_11: Optional[AnyToPrimitive] = None
I_12: Optional[AnyToPrimitive] = None
I_13: Optional[AnyToPrimitive] = None
I_22: Optional[AnyToPrimitive] = None
I_23: Optional[AnyToPrimitive] = None
I_33: Optional[AnyToPrimitive] = None
burn_area: Optional[AnyToPrimitive] = None
burn_rate: Optional[AnyToPrimitive] = None
burn_time: Optional[AnyToPrimitive] = None
center_of_mass: Optional[AnyToPrimitive] = None
center_of_propellant_mass: Optional[AnyToPrimitive] = None
exhaust_velocity: Optional[AnyToPrimitive] = None
grain_height: Optional[AnyToPrimitive] = None
grain_volume: Optional[AnyToPrimitive] = None
grain_inner_radius: Optional[AnyToPrimitive] = None
mass_flow_rate: Optional[AnyToPrimitive] = None
propellant_I_11: Optional[AnyToPrimitive] = None
propellant_I_12: Optional[AnyToPrimitive] = None
propellant_I_13: Optional[AnyToPrimitive] = None
propellant_I_22: Optional[AnyToPrimitive] = None
propellant_I_23: Optional[AnyToPrimitive] = None
propellant_I_33: Optional[AnyToPrimitive] = None
propellant_mass: Optional[AnyToPrimitive] = None
reshape_thrust_curve: Optional[AnyToPrimitive] = None
total_mass: Optional[AnyToPrimitive] = None
total_mass_flow_rate: Optional[AnyToPrimitive] = None
thrust: Optional[AnyToPrimitive] = None
nozzle_position: Optional[float] = None
coordinate_system_orientation: Optional[str] = None
motor_kind: Optional[str] = None
interpolate: Optional[str] = None
# Function attributes
# discretized by rocketpy_encoder
# serialized by RocketPyEncoder
Kn: Optional[Any] = None
I_11: Optional[Any] = None
I_12: Optional[Any] = None
I_13: Optional[Any] = None
I_22: Optional[Any] = None
I_23: Optional[Any] = None
I_33: Optional[Any] = None
burn_area: Optional[Any] = None
burn_rate: Optional[Any] = None
burn_time: Optional[Any] = None
center_of_mass: Optional[Any] = None
center_of_propellant_mass: Optional[Any] = None
exhaust_velocity: Optional[Any] = None
grain_height: Optional[Any] = None
grain_volume: Optional[Any] = None
grain_inner_radius: Optional[Any] = None
mass_flow_rate: Optional[Any] = None
propellant_I_11: Optional[Any] = None
propellant_I_12: Optional[Any] = None
propellant_I_13: Optional[Any] = None
propellant_I_22: Optional[Any] = None
propellant_I_23: Optional[Any] = None
propellant_I_33: Optional[Any] = None
propellant_mass: Optional[Any] = None
reshape_thrust_curve: Optional[Any] = None
total_mass: Optional[Any] = None
total_mass_flow_rate: Optional[Any] = None
thrust: Optional[Any] = None
++ b/src/views/motor.py
@@ class MotorSimulation(ApiBaseView):
# Core Motor attributes (always present)
burn_start_time: Optional[float] = None
burn_out_time: Optional[float] = None
dry_mass: Optional[float] = None
dry_inertia: Optional[tuple] = None
center_of_dry_mass_position: Optional[float] = None
grains_center_of_mass_position: Optional[float] = None
grain_number: Optional[int] = None
grain_density: Optional[float] = None
grain_outer_radius: Optional[float] = None
grain_initial_inner_radius: Optional[float] = None
grain_initial_height: Optional[float] = None
nozzle_radius: Optional[float] = None
throat_radius: Optional[float] = None
nozzle_position: Optional[float] = None
coordinate_system_orientation: Optional[str] = None
motor_kind: Optional[str] = None
interpolation_method: Optional[str] = None
# Function attributes
# discretized by rocketpy_encoder
# serialized by RocketPyEncoder
Kn: Optional[Any] = None
I_11: Optional[Any] = None
I_12: Optional[Any] = None
I_13: Optional[Any] = None
I_22: Optional[Any] = None
I_23: Optional[Any] = None
I_33: Optional[Any] = None
burn_area: Optional[Any] = None
burn_rate: Optional[Any] = None
- burn_time: Optional[Any] = None
center_of_mass: Optional[Any] = None
center_of_propellant_mass: Optional[Any] = None
exhaust_velocity: Optional[Any] = None
grain_height: Optional[Any] = None
grain_volume: Optional[Any] = None
grain_inner_radius: Optional[Any] = None
mass_flow_rate: Optional[Any] = None
propellant_I_11: Optional[Any] = None
propellant_I_12: Optional[Any] = None
propellant_I_13: Optional[Any] = None
propellant_I_22: Optional[Any] = None
propellant_I_23: Optional[Any] = None
propellant_I_33: Optional[Any] = None
propellant_mass: Optional[Any] = None
reshape_thrust_curve: Optional[Any] = None
total_mass: Optional[Any] = None
total_mass_flow_rate: Optional[Any] = None
thrust: Optional[Any] = None
🤖 Prompt for AI Agents
In src/views/motor.py lines 1-74: the MotorSimulation view declares a field
named interpolate (should be interpolation_method) and also declares a
function-like field burn_time which conflicts with scalar
burn_start_time/burn_out_time; rename the attribute interpolate to
interpolation_method (keep type Optional[str] and default None) and remove the
burn_time function-attribute entirely (delete its Optional[Any] declaration),
then run a quick grep across view subclasses (e.g., src/views/rocket.py and
other files under src/views) to ensure no other classes still declare
interpolate or a burn_time function field and update them similarly if found.

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