Skip to content

Conversation

@aasitvora99
Copy link
Contributor

@aasitvora99 aasitvora99 commented Aug 31, 2025

Fixes /simulate route long response time by limiting the samples to 25
Temprary workaround for jarvis's alpha release.

Summary by CodeRabbit

  • New Features

    • Added optional fin parameters for sweep length and sweep angle.
    • Added dedicated messages for flight creation and retrieval responses.
    • Expanded flight API responses with additional performance, stability, timing, and wind metrics.
  • Changes

    • center_of_mass_without_motor now accepts decimal values.
    • Drag curve fields now accept more flexible data formats.
    • In flight API responses, environment field renamed to env.
  • Refactor

    • Streamlined flight API schema and improved serialized payload handling.
  • Chores

    • Added VSCode to .gitignore.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 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.

Walkthrough

Refactors serialization to a size-aware encoder and attribute collection (InfinityEncoder + collect_attributes), replaces rocketpy_encoder/DiscretizeConfig usage across services, expands flight API schema and renames environment→env, adds fins sweep fields, relaxes some rocket drag types, and changes RocketModel.center_of_mass_without_motor from int to float.

Changes

Cohort / File(s) Summary
Models adjustments
src/models/rocket.py, src/models/sub/aerosurfaces.py
RocketModel.center_of_mass_without_motor type changed from intfloat. Fins adds optional sweep_length: Optional[float] = None and sweep_angle: Optional[float] = None.
Services encoding refactor
src/services/environment.py, src/services/flight.py, src/services/motor.py, src/services/rocket.py
Replaced rocketpy_encoder + DiscretizeConfig encoding with collect_attributes(..., [<SimulationClasses>]); updated imports and local variable names to encoded_attributes; Simulation instances now constructed via **encoded_attributes. No public method signatures changed.
Utils serialization overhaul
src/utils.py
Adds InfinityEncoder (size-aware encoder), updates rocketpy_encoder(obj) signature to use InfinityEncoder (drops config param), and adds collect_attributes(obj, attribute_classes=None) to merge encoded facets. Minor DiscretizeConfig classmethod signature/text updates.
Flight view schema shift
src/views/flight.py
FlightSimulation now derives from ApiBaseView; environment renamed to env. Adds many optional flight metric fields (speeds, times, Reynolds, tolerances, stability margins, rail forces, wind fields, etc.) and new API classes FlightCreated and FlightRetrieved. Config updated to ignore unknown fields.
Rocket view type relaxation
src/views/rocket.py
RocketSimulation.power_off_drag and power_on_drag type widened from Optional[list[tuple[float, float]]]Optional[Any].
Repo config
.gitignore
Adds # VSCode config and .vscode/ entry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Service as Service (env/flight/motor/rocket)
  participant Utils as collect_attributes
  participant Encoder as InfinityEncoder
  participant Model as Simulation (Env/Rocket/Motor/Flight)

  Client->>Service: get_*_simulation()
  Service->>Utils: collect_attributes(self.obj, [TargetClasses])
  Utils->>Encoder: rocketpy_encoder(obj)
  Encoder-->>Utils: encoded JSON/dict (size-aware)
  Utils-->>Service: encoded_attributes
  Service->>Model: Model(**encoded_attributes)
  Model-->>Service: Simulation instance
  Service-->>Client: return Simulation
  note right of Encoder: down-samples large Function/Flight solutions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Gui-FernandesBR
  • phmbressan

Poem

I hop through bytes with ears held high,
Encoders trim the data sky. 🌕
Fins gain sweep and flights say "env",
Collected attributes all in a pen.
Thump-thump—ship it, spring! 🐇🚀

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/too-large-encoding

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 or @coderabbit 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.

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 refactors the class encoding system for RocketPy simulations to address performance issues in the /simulate route by limiting function samples to 25 points. The changes introduce a new InfinityEncoder that reduces data size and replaces the previous DiscretizeConfig-based approach with a more streamlined collect_attributes function.

Key changes:

  • Replace DiscretizeConfig and rocketpy_encoder with InfinityEncoder and collect_attributes
  • Limit function discretization to 25 samples maximum to improve response times
  • Update FlightSimulation class hierarchy and add new flight attributes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/views/rocket.py Change drag attributes from typed lists to Any type
src/views/flight.py Major refactor of FlightSimulation class - changed inheritance, added many new attributes, fixed duplicates
src/utils.py Complete rewrite of encoding logic with new InfinityEncoder and collect_attributes function
src/services/rocket.py Update to use new collect_attributes function
src/services/motor.py Update to use new collect_attributes function
src/services/flight.py Update to use new collect_attributes function
src/services/environment.py Update to use new collect_attributes function
src/models/sub/aerosurfaces.py Add new fin attributes sweep_length and sweep_angle
src/models/rocket.py Fix type annotation for center_of_mass_without_motor

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +29 to +30
inclination: Optional[float] = None
heading: Optional[float] = None
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

Duplicate attribute definitions for inclination and heading. These attributes are already defined on lines 27-28, creating redundant field declarations.

Suggested change
inclination: Optional[float] = None
heading: Optional[float] = None

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

true, we should remove this

mutate_self=False,
)
if isinstance(obj, Flight):
obj._Flight__evaluate_post_process
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

This line accesses a private method but doesn't call it or assign the result. It should either call the method with parentheses obj._Flight__evaluate_post_process() or remove this line if it's not needed.

Suggested change
obj._Flight__evaluate_post_process
obj._Flight__evaluate_post_process()

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems to be a legit statement from copilot

Copy link
Contributor

Choose a reason for hiding this comment

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

The method is actually a cached property. It needs to be called at this step to make sure the Flight has its value cached for internal usage. Note: when I pop some of its attributes at the end to reduce size, this property would break if called after, caching it prevents this problem.

except Exception:
continue
return attributes
return rocketpy_encoder(attributes)
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

This creates infinite recursion since collect_attributes calls rocketpy_encoder at line 121, which returns the result of collect_attributes. The return should likely be just attributes instead.

Suggested change
return rocketpy_encoder(attributes)
return attributes

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure I got what copilot is trying to say but I think return here yields something like rocketpy_encoder(rocketpy_encoder(obj).modified())

.modified() being just an illustrative made up noop to emphasize something changed after instantiation.

is that really what we want?

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

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

75-75: Fix the missing function call parentheses

This line accesses the private method but doesn't call it. Add parentheses to actually execute the post-processing.

-            obj._Flight__evaluate_post_process
+            obj._Flight__evaluate_post_process()

200-200: Fix infinite recursion issue

The function returns rocketpy_encoder(attributes) which will cause infinite recursion since rocketpy_encoder calls collect_attributes at line 123. The return should be just attributes.

-    return rocketpy_encoder(attributes)
+    return attributes
src/views/flight.py (1)

23-31: Remove duplicate field declarations

inclination and heading are defined twice; keep a single definition to avoid confusion and tooling warnings. Also remove the duplicate “Core Flight attributes” header.

-    # Core Flight attributes (always present)
-
     # Core Flight attributes (always present)
     rail_length: Optional[float] = None
     inclination: Optional[float] = None
     heading: Optional[float] = None
-    inclination: Optional[float] = None
-    heading: Optional[float] = None
🧹 Nitpick comments (4)
src/views/rocket.py (1)

34-35: Restrict power_off_drag and power_on_drag in src/views/rocket.py to a specific union rather than Any
Widening these from Optional[list[tuple[float, float]]] to Optional[Any] removes the compile-time checks that your service and model (which expect List[Tuple[float, float]]) rely on; if you need to accept multiple encodings, use a union type (e.g. Optional[List[Tuple[float, float]] | Dict[str, Any]]) to preserve type safety.

src/utils.py (1)

57-95: Consider extracting the magic number 25

The value 25 appears multiple times in the InfinityEncoder.default method as the target sample size. Consider extracting this as a class constant for better maintainability.

 class InfinityEncoder(RocketPyEncoder):
+    MAX_SAMPLES = 25
+    
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)

     def default(self, obj):
         if (
             isinstance(obj, Function)
             and not callable(obj.source)
             and obj.__dom_dim__ == 1
         ):
             size = len(obj._domain)
             reduction_factor = 1
-            if size > 25:
-                reduction_factor = size // 25
+            if size > self.MAX_SAMPLES:
+                reduction_factor = size // self.MAX_SAMPLES
             if reduction_factor > 1:
                 obj = obj.set_discrete(
                     obj.x_array[0],
                     obj.x_array[-1],
                     size // reduction_factor,
                     mutate_self=False,
                 )
         if isinstance(obj, Flight):
             obj._Flight__evaluate_post_process()
             solution = np.array(obj.solution)
             size = len(solution)
-            if size > 25:
-                reduction_factor = size // 25
+            if size > self.MAX_SAMPLES:
+                reduction_factor = size // self.MAX_SAMPLES
                 reduced_solution = np.zeros(
                     (size // reduction_factor, solution.shape[1])
                 )
src/services/rocket.py (1)

20-21: Drop MotorSimulation from collect_attributes here; it’s a no-op for Rocket objects

collect_attributes’ MotorSimulation branch expects obj.rocket.motor, so passing MotorSimulation with a Rocket object contributes nothing and can mislead future readers. Trim it (and the unused import) for clarity.

-from src.views.motor import MotorSimulation
-from src.utils import collect_attributes
+from src.utils import collect_attributes
@@
-        encoded_attributes = collect_attributes(
-            self.rocket, [RocketSimulation, MotorSimulation]
-        )
+        encoded_attributes = collect_attributes(self.rocket, [RocketSimulation])

Also applies to: 111-115

src/views/flight.py (1)

11-16: Fix outdated docstring about base classes

Class no longer inherits from RocketSimulation/EnvironmentSimulation; it now inherits ApiBaseView. Update the docstring to match the implementation.

-    Inherits from both RocketSimulation and EnvironmentSimulation, and adds flight-specific
-    attributes. Uses the new rocketpy_encoder which may return different attributes based
-    on the actual RocketPy Flight object. The model allows extra fields to accommodate
-    any new attributes that might be encoded.
+    API-focused view for RocketPy Flight attributes.
+    Attributes are collected via collect_attributes and encoded by the RocketPy encoder.
+    Designed to accommodate evolving fields from the underlying simulation.
📜 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 5504668 and f4ddde1.

📒 Files selected for processing (9)
  • src/models/rocket.py (1 hunks)
  • src/models/sub/aerosurfaces.py (1 hunks)
  • src/services/environment.py (2 hunks)
  • src/services/flight.py (2 hunks)
  • src/services/motor.py (2 hunks)
  • src/services/rocket.py (2 hunks)
  • src/utils.py (8 hunks)
  • src/views/flight.py (6 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/services/motor.py
🧬 Code graph analysis (8)
src/services/environment.py (2)
src/utils.py (1)
  • collect_attributes (116-200)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/models/rocket.py (1)
src/routes/rocket.py (1)
  • update_rocket (56-68)
src/services/flight.py (5)
src/views/rocket.py (1)
  • RocketSimulation (8-59)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/utils.py (1)
  • collect_attributes (116-200)
src/views/flight.py (1)
  • FlightSimulation (9-140)
src/views/rocket.py (2)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/services/motor.py (2)
src/utils.py (1)
  • collect_attributes (116-200)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/services/rocket.py (3)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/utils.py (1)
  • collect_attributes (116-200)
src/views/rocket.py (1)
  • RocketSimulation (8-59)
src/utils.py (4)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/views/flight.py (1)
  • FlightSimulation (9-140)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/views/rocket.py (1)
  • RocketSimulation (8-59)
src/views/flight.py (4)
src/views/interface.py (1)
  • ApiBaseView (4-6)
src/views/rocket.py (1)
  • RocketSimulation (8-59)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/models/flight.py (1)
  • FlightModel (7-71)
🪛 GitHub Actions: Pylint
src/utils.py

[warning] 57-57: pylint: W0237: Parameter 'o' has been renamed to 'obj' in overriding 'InfinityEncoder.default' method (arguments-renamed)


[warning] 75-75: pylint: W0104: Statement seems to have no effect (pointless-statement)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 116-116: pylint: R0912: Too many branches (23/12) (too-many-branches)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 303-303: pylint: W0101: Unreachable code (unreachable)


[warning] 316-316: pylint: W0101: Unreachable code (unreachable)


[warning] 329-329: pylint: W0101: Unreachable code (unreachable)

🔇 Additional comments (5)
src/models/rocket.py (1)

22-22: Type correction looks good!

The change from int to float for center_of_mass_without_motor is appropriate as center of mass values are typically floating-point measurements in physical simulations.

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

44-45: New sweep parameters for fins look good!

The addition of sweep_length and sweep_angle as optional float parameters is appropriate for modeling swept fins in aerodynamic simulations. These will be properly handled by the existing get_additional_parameters method.

src/services/environment.py (1)

57-61: LGTM! Clean refactor to use the new encoding system.

The change to use collect_attributes aligns well with the overall PR's encoding refactor. The implementation correctly passes the EnvironmentSimulation class for attribute collection.

src/services/motor.py (1)

143-148: LGTM on switching to collect_attributes

Centralizes encoding and keeps the public API unchanged. Looks good.

src/services/flight.py (1)

61-71: Risk: FlightSimulation(extra='ignore') may drop aggregated fields

You now aggregate many attributes via collect_attributes, but FlightSimulation is configured with extra='ignore'. Any unexpected/new fields will be discarded. Either update FlightSimulation to extra='allow' (preferred) or ensure collect_attributes outputs only declared fields.

Comment on lines +116 to 199
def collect_attributes(obj, attribute_classes=None):
"""
Collect attributes from various simulation classes and populate them from the flight object.
"""
if attribute_classes is None:
attribute_classes = []

attributes = rocketpy_encoder(obj)

for attribute_class in attribute_classes:
if issubclass(attribute_class, FlightSimulation):
flight_attributes_list = [
attr
for attr in attribute_class.__annotations__.keys()
if attr not in ["message", "rocket", "env"]
]
try:
discretized_func = Function(attr_value.source)
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)

except Exception as e:
logger.warning(f"Failed to discretize {attr_name}: {e}")
for key in flight_attributes_list:
if key not in attributes:
try:
value = getattr(obj, key)
attributes[key] = value
except Exception:
pass
except Exception:
pass

elif issubclass(attribute_class, RocketSimulation):
rocket_attributes_list = [
attr
for attr in attribute_class.__annotations__.keys()
if attr not in ["message", "motor"]
]
try:
for key in rocket_attributes_list:
if key not in attributes.get("rocket", {}):
try:
value = getattr(obj.rocket, key)
attributes.setdefault("rocket", {})[key] = value
except Exception:
pass
except Exception:
pass

elif issubclass(attribute_class, MotorSimulation):
motor_attributes_list = [
attr
for attr in attribute_class.__annotations__.keys()
if attr not in ["message"]
]
try:
for key in motor_attributes_list:
if key not in attributes.get("rocket", {}).get(
"motor", {}
):
try:
value = getattr(obj.rocket.motor, key)
attributes.setdefault("rocket", {}).setdefault(
"motor", {}
)[key] = value
except Exception:
pass
except Exception:
pass

elif issubclass(attribute_class, EnvironmentSimulation):
environment_attributes_list = [
attr
for attr in attribute_class.__annotations__.keys()
if attr not in ["message"]
]
try:
for key in environment_attributes_list:
if key not in attributes.get("env", {}):
try:
value = getattr(obj.env, key)
attributes.setdefault("env", {})[key] = value
except Exception:
pass
except Exception:
pass
else:
continue

Copy link
Contributor

@coderabbitai coderabbitai bot Aug 31, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and reduce complexity

The collect_attributes function has high cyclomatic complexity (23 branches) and deeply nested blocks. The broad exception catching could hide bugs. Consider:

  1. Extracting methods for each attribute class type
  2. Using more specific exception types
  3. Logging errors instead of silently passing
+import logging
+
+logger = logging.getLogger(__name__)
+
 def collect_attributes(obj, attribute_classes=None):
     """
     Collect attributes from various simulation classes and populate them from the flight object.
     """
     if attribute_classes is None:
         attribute_classes = []

     attributes = rocketpy_encoder(obj)

     for attribute_class in attribute_classes:
         if issubclass(attribute_class, FlightSimulation):
-            flight_attributes_list = [
-                attr
-                for attr in attribute_class.__annotations__.keys()
-                if attr not in ["message", "rocket", "env"]
-            ]
-            try:
-                for key in flight_attributes_list:
-                    if key not in attributes:
-                        try:
-                            value = getattr(obj, key)
-                            attributes[key] = value
-                        except Exception:
-                            pass
-            except Exception:
-                pass
+            _collect_flight_attributes(obj, attribute_class, attributes)
         elif issubclass(attribute_class, RocketSimulation):
-            # ... similar refactor
+            _collect_rocket_attributes(obj, attribute_class, attributes)
         elif issubclass(attribute_class, MotorSimulation):
-            # ... similar refactor
+            _collect_motor_attributes(obj, attribute_class, attributes)
         elif issubclass(attribute_class, EnvironmentSimulation):
-            # ... similar refactor
+            _collect_environment_attributes(obj, attribute_class, attributes)
         else:
             continue

     return attributes

+def _collect_flight_attributes(obj, attribute_class, attributes):
+    """Collect flight-specific attributes."""
+    flight_attributes_list = [
+        attr
+        for attr in attribute_class.__annotations__.keys()
+        if attr not in ["message", "rocket", "env"]
+    ]
+    for key in flight_attributes_list:
+        if key not in attributes:
+            try:
+                value = getattr(obj, key)
+                attributes[key] = value
+            except AttributeError:
+                logger.debug(f"Attribute {key} not found on flight object")
+            except Exception as e:
+                logger.warning(f"Error collecting flight attribute {key}: {e}")

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Pylint

[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)


[warning] 116-116: pylint: R0912: Too many branches (23/12) (too-many-branches)


[warning] 125-125: pylint: R1702: Too many nested blocks (6/5) (too-many-nested-blocks)

🤖 Prompt for AI Agents
src/utils.py lines 116-199: The collect_attributes function is too complex and
swallows all exceptions; refactor by extracting four small helper functions
(populate_flight_attributes(obj, attributes, attribute_class),
populate_rocket_attributes(...), populate_motor_attributes(...),
populate_environment_attributes(...)) and call them in the main loop with an
early-continue style to avoid deep nesting; replace bare except Exception blocks
with specific catches (AttributeError, TypeError, KeyError) around getattr and
issubclass checks, and surface failures by logging them (add a module logger via
import logging; logger = logging.getLogger(__name__) and use logger.exception or
logger.error) instead of silently passing; ensure helpers use
attributes.setdefault for nested dicts and avoid repeated get(..., {}) checks to
simplify logic.

Copy link
Collaborator

Choose a reason for hiding this comment

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

that is a very nice refactoring suggestion,

it modularizes the attribute collection distributing complexity out of global collection more evenly between each object

Copy link
Contributor

Choose a reason for hiding this comment

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

@GabrielBarberini Thank you! I'm glad the refactoring suggestion resonates with you. Breaking down the complex collect_attributes function into focused helper functions would indeed make the code more maintainable and easier to reason about. The modular approach also makes it easier to test each attribute collection strategy independently and provides better error visibility through specific logging.

src/utils.py Outdated
Comment on lines 299 to 303
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
await self.send(message)
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

Remove unreachable code after returns

The pipeline correctly identified unreachable code after return statements. The await self.send(message) lines at 303, 316, and 329 will never execute.

             ):
                 await self.send(self.initial_message)
-                await self.send(message)
+                return await self.send(message)
             elif not more_body:
                 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)
+                return await self.send(message)
             else:
                 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)
+                return await self.send(message)

Also applies to: 303-316, 316-329

🧰 Tools
🪛 GitHub Actions: Pylint

[warning] 303-303: pylint: W0101: Unreachable code (unreachable)

🤖 Prompt for AI Agents
In src/utils.py around lines 299-303 (and similarly 303-316 and 316-329), there
are await self.send(message) calls that are unreachable because earlier
returns/exits in the same control flow make those lines dead; remove the
unreachable await self.send(message) calls (or, if the sends are required, move
them so they execute before the returning statements) so no code after a return
remains; ensure tests/logic still send the expected messages by either deleting
the dead lines or reordering to send then return.

model_config = ConfigDict(
ser_json_exclude_none=True, extra='allow', arbitrary_types_allowed=True
)
model_config = ConfigDict(extra='ignore', arbitrary_types_allowed=True)
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

extra='ignore' contradicts dynamic schema; enable extra='allow'

Current config will drop unknown keys coming from collect_attributes, undermining the refactor’s goal of accommodating new fields. Align with other views and preserve ser_json_exclude_none.

-    model_config = ConfigDict(extra='ignore', arbitrary_types_allowed=True)
+    model_config = ConfigDict(
+        ser_json_exclude_none=True, extra='allow', arbitrary_types_allowed=True
+    )
📝 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
model_config = ConfigDict(extra='ignore', arbitrary_types_allowed=True)
model_config = ConfigDict(
ser_json_exclude_none=True, extra='allow', arbitrary_types_allowed=True
)
🤖 Prompt for AI Agents
In src/views/flight.py around line 19, the model_config currently uses
extra='ignore' which drops unknown keys from collect_attributes; change it to
extra='allow' (keeping arbitrary_types_allowed=True) and also include the
ser_json_exclude_none setting consistent with other views so unknown fields are
preserved and None fields are excluded when serializing.

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)

25-54: Remove the unused DiscretizeConfig class in src/utils.py.
No references outside its definition were found—this dead code can be safely removed.

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

303-313: Eliminate “unreachable” lints by returning after send

These lines are flagged W0101. Return after sending both messages to end the branch cleanly.

                 await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
+                await self.send(message)
+                return
@@
                 await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
+                await self.send(message)
+                return
@@
                 await self.send(self.initial_message)
-                await self.send(message)  # pylint: disable=unreachable
+                await self.send(message)
+                return

Also applies to: 319-327, 334-341


77-97: Call post-process, avoid in-place mutation, enforce ≤25, replace SciPy interp

  • Missing call: _Flight__evaluate_post_process is not invoked.
  • In-place mutation of Flight.solution/flight_phases/function_evaluations inside an encoder risks state corruption for callers.
  • Same floor-div bug as above; use a fixed target_samples.
  • Replace interp1d with np.interp to drop SciPy.
-        if isinstance(obj, Flight):
-            obj._Flight__evaluate_post_process
-            solution = np.array(obj.solution)
-            size = len(solution)
-            if size > 25:
-                reduction_factor = size // 25
-                reduced_solution = np.zeros(
-                    (size // reduction_factor, solution.shape[1])
-                )
-                reduced_scale = np.linspace(
-                    solution[0, 0], solution[-1, 0], size // reduction_factor
-                )
-                for i, col in enumerate(solution.T):
-                    reduced_solution[:, i] = interp1d(
-                        solution[:, 0], col, assume_sorted=True
-                    )(reduced_scale)
-                obj.solution = reduced_solution.tolist()
-
-            obj.flight_phases = None
-            obj.function_evaluations = None
+        if isinstance(obj, Flight):
+            obj._Flight__evaluate_post_process()
+            solution = np.array(obj.solution)
+            size = len(solution)
+            target_samples = 25 if size > 25 else size
+            # snapshot originals to avoid leaking mutations
+            orig_solution = obj.solution
+            orig_fp = getattr(obj, "flight_phases", None)
+            orig_fe = getattr(obj, "function_evaluations", None)
+            try:
+                if target_samples and target_samples != size:
+                    reduced_solution = np.zeros((target_samples, solution.shape[1]))
+                    reduced_scale = np.linspace(solution[0, 0], solution[-1, 0], target_samples)
+                    for i, col in enumerate(solution.T):
+                        reduced_solution[:, i] = np.interp(reduced_scale, solution[:, 0], col)
+                    obj.solution = reduced_solution.tolist()
+                # Always mask heavy internals
+                obj.flight_phases = None
+                obj.function_evaluations = None
+                return super().default(obj)
+            finally:
+                obj.solution = orig_solution
+                obj.flight_phases = orig_fp
+                obj.function_evaluations = orig_fe
🧹 Nitpick comments (6)
src/utils.py (6)

11-13: Prefer public import path for RocketPyEncoder

Importing from a private module (rocketpy._encoders) is brittle across versions. If available, switch to a public path.

Would this work in your environment?

-from rocketpy._encoders import RocketPyEncoder
+from rocketpy.encoders import RocketPyEncoder  # or: from rocketpy import RocketPyEncoder

8-10: Drop SciPy dependency; use numpy.interp

This path only needs 1D interpolation; np.interp is sufficient and avoids a heavy dependency/import on request paths.

Apply:

-import numpy as np
-from scipy.interpolate import interp1d
+import numpy as np

(see Flight reduction change below for replacing interp1d.)


56-59: Unnecessary init override

InfinityEncoder.__init__ just calls super; you can drop it.

-class InfinityEncoder(RocketPyEncoder):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+class InfinityEncoder(RocketPyEncoder):
     ...

60-61: Match base signature to silence Pylint W0237

Rename parameter to match the base encoder (default(self, o)).

-    def default(self, obj):
+    def default(self, o):
+        obj = o

119-204: Simplify collect_attributes; narrow exception scopes

Current broad try/except blocks hide real bugs and trigger pylint complexity warnings. Narrow to AttributeError and drop outer no-op catches where possible.

-            try:
-                for key in flight_attributes_list:
-                    if key not in attributes:
-                        try:
-                            value = getattr(obj, key)
-                            attributes[key] = value
-                        except Exception:
-                            pass
-            except Exception:
-                pass
+            for key in flight_attributes_list:
+                if key not in attributes:
+                    try:
+                        attributes[key] = getattr(obj, key)
+                    except AttributeError:
+                        pass
@@
-            try:
-                for key in rocket_attributes_list:
-                    if key not in attributes.get("rocket", {}):
-                        try:
-                            value = getattr(obj.rocket, key)
-                            attributes.setdefault("rocket", {})[key] = value
-                        except Exception:
-                            pass
-            except Exception:
-                pass
+            for key in rocket_attributes_list:
+                if key not in attributes.get("rocket", {}):
+                    try:
+                        value = getattr(obj.rocket, key)
+                        attributes.setdefault("rocket", {})[key] = value
+                    except AttributeError:
+                        pass
@@
-            try:
-                for key in motor_attributes_list:
-                    if key not in attributes.get("rocket", {}).get(
-                        "motor", {}
-                    ):
-                        try:
-                            value = getattr(obj.rocket.motor, key)
-                            attributes.setdefault("rocket", {}).setdefault(
-                                "motor", {}
-                            )[key] = value
-                        except Exception:
-                            pass
-            except Exception:
-                pass
+            for key in motor_attributes_list:
+                if key not in attributes.get("rocket", {}).get("motor", {}):
+                    try:
+                        value = getattr(obj.rocket.motor, key)
+                        attributes.setdefault("rocket", {}).setdefault("motor", {})[key] = value
+                    except AttributeError:
+                        pass
@@
-            try:
-                for key in environment_attributes_list:
-                    if key not in attributes.get("env", {}):
-                        try:
-                            value = getattr(obj.env, key)
-                            attributes.setdefault("env", {})[key] = value
-                        except Exception:
-                            pass
-            except Exception:
-                pass
+            for key in environment_attributes_list:
+                if key not in attributes.get("env", {}):
+                    try:
+                        value = getattr(obj.env, key)
+                        attributes.setdefault("env", {})[key] = value
+                    except AttributeError:
+                        pass

Also verify that returning rocketpy_encoder(attributes) won’t recurse (looks safe now).


101-116: Add backward-compatibility and verify allow_pickle support

  • Change rocketpy_encoder signature to accept a config=None parameter (ignored) to avoid breaking callers.
  • Confirm that allow_pickle is accepted by InfinityEncoder/RocketPyEncoder (native json.dumps doesn’t support it by default).
-def rocketpy_encoder(obj):
+def rocketpy_encoder(obj, config=None):
     """
     Encode a RocketPy object using official RocketPy encoders.
+    `config` is accepted for backward compatibility and ignored.
     """
     json_str = json.dumps(
         obj,
         cls=InfinityEncoder,
         include_outputs=True,
         include_function_data=True,
         discretize=True,
-        allow_pickle=False,
+        allow_pickle=False,  # ensure support in InfinityEncoder/RocketPyEncoder
     )
📜 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 f4ddde1 and de30435.

📒 Files selected for processing (2)
  • .gitignore (1 hunks)
  • src/utils.py (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils.py (8)
src/services/environment.py (3)
  • environment (42-43)
  • environment (46-47)
  • get_environment_simulation (49-61)
src/views/environment.py (1)
  • EnvironmentSimulation (13-78)
src/services/flight.py (3)
  • flight (47-48)
  • flight (51-52)
  • get_flight_simulation (51-62)
src/views/flight.py (2)
  • FlightSimulation (9-140)
  • FlightSimulation (9-107)
src/services/motor.py (3)
  • motor (129-130)
  • motor (133-134)
  • get_motor_simulation (136-145)
src/views/motor.py (1)
  • MotorSimulation (7-73)
src/services/rocket.py (3)
  • rocket (97-98)
  • rocket (101-102)
  • get_rocket_simulation (103-114)
src/views/rocket.py (1)
  • RocketSimulation (8-59)
🪛 GitHub Actions: Pylint
src/utils.py

[warning] 60-60: Pylint: W0237 - Parameter 'o' has been renamed to 'obj' in overriding 'InfinityEncoder.default' method (arguments-renamed).


[warning] 78-78: Pylint: W0104 - Statement seems to have no effect (pointless-statement).


[warning] 119-119: Pylint: R0912 - Too many branches (23/12) (too-many-branches).


[warning] 128-128: Pylint: R1702 - Too many nested blocks (6/5) (too-many-nested-blocks).


[warning] 128-128: Pylint: R1702 - Too many nested blocks (6/5) (too-many-nested-blocks).


[warning] 128-128: Pylint: R1702 - Too many nested blocks (6/5) (too-many-nested-blocks).

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

17-21: LGTM: updated view imports align with attribute collection

The imports for EnvironmentSimulation, FlightSimulation, MotorSimulation, and RocketSimulation look consistent with the new collection flow.

Comment on lines +61 to +77
if (
isinstance(obj, Function)
and not callable(obj.source)
and obj.__dom_dim__ == 1
):
size = len(obj._domain)
reduction_factor = 1
if size > 25:
reduction_factor = size // 25
if reduction_factor > 1:
obj = obj.set_discrete(
obj.x_array[0],
obj.x_array[-1],
size // reduction_factor,
mutate_self=False,
)
if isinstance(obj, Flight):
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

Function downsampling doesn’t enforce ≤25 for sizes 26–49; avoid private attr

size // 25 floors to 1 when 26 ≤ size ≤ 49, so no reduction happens. Also prefer public arrays over _domain when available.

-        if (
+        if (
             isinstance(obj, Function)
             and not callable(obj.source)
             and obj.__dom_dim__ == 1
         ):
-            size = len(obj._domain)
-            reduction_factor = 1
-            if size > 25:
-                reduction_factor = size // 25
-            if reduction_factor > 1:
-                obj = obj.set_discrete(
-                    obj.x_array[0],
-                    obj.x_array[-1],
-                    size // reduction_factor,
-                    mutate_self=False,
-                )
+            size = len(getattr(obj, "x_array", [])) or len(getattr(obj, "_domain", []))
+            target_samples = 25 if size > 25 else size
+            if target_samples and target_samples != size:
+                obj = obj.set_discrete(
+                    obj.x_array[0],
+                    obj.x_array[-1],
+                    target_samples,
+                    mutate_self=False,
+                )
📝 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
if (
isinstance(obj, Function)
and not callable(obj.source)
and obj.__dom_dim__ == 1
):
size = len(obj._domain)
reduction_factor = 1
if size > 25:
reduction_factor = size // 25
if reduction_factor > 1:
obj = obj.set_discrete(
obj.x_array[0],
obj.x_array[-1],
size // reduction_factor,
mutate_self=False,
)
if isinstance(obj, Flight):
if (
isinstance(obj, Function)
and not callable(obj.source)
and obj.__dom_dim__ == 1
):
size = len(getattr(obj, "x_array", [])) or len(getattr(obj, "_domain", []))
target_samples = 25 if size > 25 else size
if target_samples and target_samples != size:
obj = obj.set_discrete(
obj.x_array[0],
obj.x_array[-1],
target_samples,
mutate_self=False,
)
if isinstance(obj, Flight):
🤖 Prompt for AI Agents
In src/utils.py around lines 61–77, the downsampling logic uses size =
len(obj._domain) and reduction_factor = size // 25 which floors to 1 for sizes
26–49 and also accesses a private _domain; change to compute size from a public
attribute when available (e.g. getattr(obj, "domain", getattr(obj, "x_array",
obj._domain))) and compute reduction_factor = math.ceil(size / 25) (import math
if needed), then apply downsampling only when reduction_factor > 1 using the
computed reduction_factor (keeping the existing set_discrete call but using the
new size calculation) so sizes 26–49 are correctly reduced and private
attributes are avoided when possible.


message: str = "Flight successfully simulated"

# Core Flight attributes (always present)
Copy link
Collaborator

Choose a reason for hiding this comment

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

duplicated comment?

):
size = len(obj._domain)
reduction_factor = 1
if size > 25:
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe this can be simplified to single line with ternary approach

attributes.setdefault("rocket", {})[key] = value
except Exception:
pass
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think there is space for any exception in the search, there is explicit default to {} already if 'rocket' is not found,

so I think we should be able to remove this extra try catch

also, I would prefer to be more specific if possible to which exception we are comfortable skipping and which we need to raise, maybe not all of them should be skipped (at the expense of possibly compromising simulation integrity)

)[key] = value
except Exception:
pass
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above

attributes.setdefault("env", {})[key] = value
except Exception:
pass
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above

return _fix_datetime_fields(encoded_result)


def collect_attributes(obj, attribute_classes=None):
Copy link
Collaborator

Choose a reason for hiding this comment

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

would you mind to set the signature as Union[FlightSim, EnvSim, ...) ?

Copy link
Collaborator

@GabrielBarberini GabrielBarberini left a comment

Choose a reason for hiding this comment

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

Thank you for the enhancement @aasitvora99 !

Globally I think we finally have the opportunity to take major advantage of the fact we use latest python version with pydantic and use some of its fancy pattern matching capabilities instead of the if elifs, see a quick example here: https://youtu.be/MfUuHYi2jgY?t=1204

What would you say to that idea?

Aside from that I left just some minor comments

@GabrielBarberini GabrielBarberini merged commit da0bdb0 into master Sep 21, 2025
3 of 4 checks passed
@GabrielBarberini GabrielBarberini deleted the fix/too-large-encoding branch September 21, 2025 08:57
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.

4 participants