Skip to content

Conversation

@Marchma0
Copy link

@Marchma0 Marchma0 commented Nov 4, 2025

Pull request type

  • Code changes (bugfix, features)
  • Code maintenance (refactoring, formatting, tests)

Checklist

  • Tests for the changes have been added (if needed)
  • Lint (black rocketpy/ tests/) has passed locally
  • All tests (pytest tests -m slow --runslow) have passed locally

Current behavior

#661

New behavior

We added the function to load a motor from the thrustcruve API and we tested it.

Breaking change

  • No

@Marchma0 Marchma0 requested a review from a team as a code owner November 4, 2025 18:09
@Gui-FernandesBR Gui-FernandesBR changed the base branch from master to develop November 4, 2025 18:20
@Gui-FernandesBR
Copy link
Member

I really like this implementation!!

Copy link

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 adds a new method load_from_thrustcurve_api to the GenericMotor class that allows users to download motor data directly from the ThrustCurve.org API by providing a motor name. This feature simplifies motor initialization by eliminating the need to manually download .eng files.

  • Adds API integration with ThrustCurve.org to download motor data
  • Implements a new static method that searches for motors and downloads their .eng files
  • Includes comprehensive test coverage for the new functionality

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
rocketpy/motors/motor.py Adds new static method load_from_thrustcurve_api with required imports (base64, tempfile, requests) to enable downloading and loading motor data from ThrustCurve API
tests/unit/motors/test_genericmotor.py Adds test case to verify the new API loading functionality works correctly with Cesaroni M1670 motor data

@Gui-FernandesBR
Copy link
Member

I see 2 major problems:

1 - Documentation: we should add a description of how to use the new feature.
2 - Tests: We should mock the API call so we don't exhaust the endpoint whenever we run the tests

@Monta120 Monta120 force-pushed the enh/motor-thrustcurve-api branch from ff57272 to 2770ba2 Compare November 4, 2025 21:32
@Monta120 Monta120 force-pushed the enh/motor-thrustcurve-api branch from 2770ba2 to da39fcb Compare November 4, 2025 21:57
@Gui-FernandesBR Gui-FernandesBR requested review from Monta120, Copilot and phmbressan and removed request for Monta120 November 5, 2025 01:34
Copy link

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

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

@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

❌ Patch coverage is 95.12195% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.31%. Comparing base (9cf3dd4) to head (361ffab).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
rocketpy/motors/motor.py 95.12% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #870      +/-   ##
===========================================
+ Coverage    80.27%   80.31%   +0.04%     
===========================================
  Files          104      104              
  Lines        12769    12809      +40     
===========================================
+ Hits         10250    10288      +38     
- Misses        2519     2521       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Monta120
Copy link

Monta120 commented Nov 5, 2025

@Gui-FernandesBR Thank you for going over the code. I just updated the latest commit to run make format for import/order/style cleanup and added tests for exception handling in load_from_thrustcurve_api.

@Marchma0
Copy link
Author

Hi all, We’ve updated the PR based on your feedback. Could you please check if it looks good now or if anything else needs improvement ?

@Gui-FernandesBR
Copy link
Member

Gui-FernandesBR commented Nov 12, 2025

Hi all, We’ve updated the PR based on your feedback. Could you please check if it looks good now or if anything else needs improvement ?

@Marchma0 can you check why linters are not passing? Also, if you could please mark previous comments as "resolved", that would make our review easier

@Marchma0
Copy link
Author

We updated the code, i think we resolved pylint errors.

@Marchma0
Copy link
Author

Do you think there is something else to modify ? @Gui-FernandesBR

@Marchma0 Marchma0 force-pushed the enh/motor-thrustcurve-api branch from 29a7aaa to 142eaf8 Compare November 17, 2025 14:56
@Marchma0
Copy link
Author

Sorry for the last commit, it should be good now. Can you check @Gui-FernandesBR please

when the user does not have all the information required to build a ``SolidMotor`` yet.

The ``load_from_thrustcurve_api`` method
---------------------------------------
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
---------------------------------------
----------------------------------------

Comment on lines +122 to +123
prefer using local `.eng` files.
Signature
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
prefer using local `.eng` files.
Signature
prefer using local `.eng` files.
Signature

Copy link
Member

@Gui-FernandesBR Gui-FernandesBR left a comment

Choose a reason for hiding this comment

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

Very good implementation.

Please update the CHANGELOG so we can proceed with the merge.

For future developments... I don't think the file should really be temporary...
We should save the downloaded file in a cache folder so we reuse it in future calls instead of downloading through the API again.

Would you like to open a new PR implementing that?

I know OSMnx has a very good cache system that could somehow inspire this implementation: https://github.com/gboeing/osmnx

Copy link

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

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

---------------
- The method first performs a search on ThrustCurve using the provided name.
If no results are returned a :class:`ValueError` is raised.
- If a motor is found the method requests the .eng file in RASP format, decodes
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The phrase "If a motor is found the method requests" is missing a comma after "found". It should read: "If a motor is found, the method requests..."

Suggested change
- If a motor is found the method requests the .eng file in RASP format, decodes
- If a motor is found, the method requests the .eng file in RASP format, decodes

Copilot uses AI. Check for mistakes.
Comment on lines +1920 to +1921
@staticmethod
def call_thrustcurve_api(name: str):
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The call_thrustcurve_api method is marked as @staticmethod but it's only called from load_from_thrustcurve_api at line 2013 using GenericMotor.call_thrustcurve_api(name). This method could be marked as a private method (e.g., _call_thrustcurve_api) since it's an internal implementation detail not intended for public use. The docstring doesn't indicate it's part of the public API, and the method name doesn't follow common naming patterns for public utility methods.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

I agree

Copy link
Collaborator

Choose a reason for hiding this comment

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

I also agree, I would not leave this method as a public member of the class. A simple _ should fix it.

Behavior notes
---------------
- The method first performs a search on ThrustCurve using the provided name.
If no results are returned a :class:`ValueError` is raised.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The phrase "If no results are returned a :class:ValueError is raised" is missing a comma after "returned". It should read: "If no results are returned, a :class:ValueError is raised."

Suggested change
If no results are returned a :class:`ValueError` is raised.
If no results are returned, a :class:`ValueError` is raised.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

true

- The method first performs a search on ThrustCurve using the provided name.
If no results are returned a :class:`ValueError` is raised.
- If a motor is found the method requests the .eng file in RASP format, decodes
it and temporarily writes it to disk; a ``GenericMotor`` is then constructed
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The phrase "it and temporarily writes it to disk; a GenericMotor is then constructed" uses a semicolon incorrectly. Consider using a period or comma instead: "it and temporarily writes it to disk. A GenericMotor is then constructed..." or "it and temporarily writes it to disk, and a GenericMotor is then constructed..."

Suggested change
it and temporarily writes it to disk; a ``GenericMotor`` is then constructed
it and temporarily writes it to disk. A ``GenericMotor`` is then constructed

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

true

Comment on lines +245 to +269
def assert_motor_specs(motor):
burn_time = (0, 3.9)
dry_mass = 2.130
propellant_initial_mass = 3.101
chamber_radius = 75 / 1000
chamber_height = 757 / 1000
nozzle_radius = chamber_radius * 0.85
average_thrust = 1545.218
total_impulse = 6026.350
max_thrust = 2200.0
exhaust_velocity = 1943.357

assert motor.burn_time == burn_time
assert motor.dry_mass == dry_mass
assert motor.propellant_initial_mass == propellant_initial_mass
assert motor.chamber_radius == chamber_radius
assert motor.chamber_height == chamber_height
assert motor.chamber_position == 0
assert motor.average_thrust == pytest.approx(average_thrust)
assert motor.total_impulse == pytest.approx(total_impulse)
assert motor.exhaust_velocity.average(*burn_time) == pytest.approx(exhaust_velocity)
assert motor.max_thrust == pytest.approx(max_thrust)
assert motor.nozzle_radius == pytest.approx(nozzle_radius)


Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The assert_motor_specs function duplicates the same test values found in the test_load_from_eng_file function (lines 148-159). This creates maintenance burden - if these expected values need to be updated, they must be changed in two places. Consider extracting these as module-level constants or creating a fixture that can be reused by both functions.

Suggested change
def assert_motor_specs(motor):
burn_time = (0, 3.9)
dry_mass = 2.130
propellant_initial_mass = 3.101
chamber_radius = 75 / 1000
chamber_height = 757 / 1000
nozzle_radius = chamber_radius * 0.85
average_thrust = 1545.218
total_impulse = 6026.350
max_thrust = 2200.0
exhaust_velocity = 1943.357
assert motor.burn_time == burn_time
assert motor.dry_mass == dry_mass
assert motor.propellant_initial_mass == propellant_initial_mass
assert motor.chamber_radius == chamber_radius
assert motor.chamber_height == chamber_height
assert motor.chamber_position == 0
assert motor.average_thrust == pytest.approx(average_thrust)
assert motor.total_impulse == pytest.approx(total_impulse)
assert motor.exhaust_velocity.average(*burn_time) == pytest.approx(exhaust_velocity)
assert motor.max_thrust == pytest.approx(max_thrust)
assert motor.nozzle_radius == pytest.approx(nozzle_radius)
# Module-level constant for expected motor specs
EXPECTED_MOTOR_SPECS = {
"burn_time": (0, 3.9),
"dry_mass": 2.130,
"propellant_initial_mass": 3.101,
"chamber_radius": 75 / 1000,
"chamber_height": 757 / 1000,
"nozzle_radius": (75 / 1000) * 0.85,
"average_thrust": 1545.218,
"total_impulse": 6026.350,
"max_thrust": 2200.0,
"exhaust_velocity": 1943.357,
"chamber_position": 0,
}
def assert_motor_specs(motor):
specs = EXPECTED_MOTOR_SPECS
assert motor.burn_time == specs["burn_time"]
assert motor.dry_mass == specs["dry_mass"]
assert motor.propellant_initial_mass == specs["propellant_initial_mass"]
assert motor.chamber_radius == specs["chamber_radius"]
assert motor.chamber_height == specs["chamber_height"]
assert motor.chamber_position == specs["chamber_position"]
assert motor.average_thrust == pytest.approx(specs["average_thrust"])
assert motor.total_impulse == pytest.approx(specs["total_impulse"])
assert motor.exhaust_velocity.average(*specs["burn_time"]) == pytest.approx(specs["exhaust_velocity"])
assert motor.max_thrust == pytest.approx(specs["max_thrust"])
assert motor.nozzle_radius == pytest.approx(specs["nozzle_radius"])

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

true


Testing advice
---------------
- ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The "Testing advice" section is incomplete. It starts with "- pytest's caplog or capfd to assert on log/warning output." which is a sentence fragment. This should be a complete sentence, such as: "- Use pytest's caplog or capfd to assert on log/warning output."

Suggested change
- ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output.
- Use ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output.

Copilot uses AI. Check for mistakes.
----------
name : str
Motor name to search on ThrustCurve (example:
``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

Missing space after the closing parenthesis and before "Only".

Suggested change
``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not
``"M1670"``). Only shorthand names are accepted (e.g. ``"M1670"``, not

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +134
``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not
``"Cesaroni M1670"``).
when multiple matches occur the first result returned by the API is used.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The first letter of "when" should be capitalized as "When" since it starts a new sentence. Also missing space after the closing parenthesis.

Suggested change
``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not
``"Cesaroni M1670"``).
when multiple matches occur the first result returned by the API is used.
``"M1670"``). Only shorthand names are accepted (e.g. ``"M1670"``, not
``"Cesaroni M1670"``).
When multiple matches occur the first result returned by the API is used.

Copilot uses AI. Check for mistakes.
ValueError
If the API search returns no motor, or if the download endpoint returns no
.eng file or empty/invalid data.
requests.exceptions.RequestException
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The requests.exceptions.RequestException line is incomplete - it's missing a description of when this exception is raised. It should include text similar to what's in the code docstring: "If a network or HTTP error occurs during the API call."

Suggested change
requests.exceptions.RequestException
requests.exceptions.RequestException
If a network or HTTP error occurs during the API call to ThrustCurve.

Copilot uses AI. Check for mistakes.

This method performs network requests to the ThrustCurve API. Use it only
when you have network access. For automated testing or reproducible runs,
prefer using local `.eng` files.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

Missing blank line before the "Signature" subsection heading. According to reStructuredText conventions, there should be a blank line between the end of a note/admonition block and the next heading.

Suggested change
prefer using local `.eng` files.
prefer using local `.eng` files.

Copilot uses AI. Check for mistakes.
@Gui-FernandesBR
Copy link
Member

I believe we are almost done. I will approave and merge the PR once you solve all the remainig comments and make sure the CHANGELOG is up to date.

Once again ty for the brillant implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

4 participants