Skip to content

Commit 40558f1

Browse files
Merge pull request #1047 from RocketPy-Team/fix/pre-release-hardening
FIX: pre release hardening
2 parents bd376e0 + 7e37cb0 commit 40558f1

41 files changed

Lines changed: 1690 additions & 397 deletions

Some content is hidden

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

.github/workflows/changelog.yml

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515

1616
steps:
1717
- name: Clone RocketPy
18-
uses: actions/checkout@main
18+
uses: actions/checkout@v4
1919
with:
2020
repository: RocketPy-Team/RocketPy
2121
ref: develop
@@ -27,31 +27,52 @@ jobs:
2727
PR_NUMBER: ${{ github.event.pull_request.number }}
2828
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
2929
run: |
30-
SECTION="### Added"
31-
PREFIX="ENH"
32-
33-
if [[ "$PR_LABELS" == *"Bug"* ]]; then
34-
SECTION="### Fixed"
35-
PREFIX="BUG"
36-
elif [[ "$PR_LABELS" == *"Refactor"* ]]; then
37-
SECTION="### Changed"
38-
PREFIX="MNT"
39-
elif [[ "$PR_LABELS" == *"Docs"* ]] && [[ "$PR_LABELS" == *"Git housekeeping"* ]]; then
40-
SECTION="### Changed"
41-
PREFIX="DOC"
42-
elif [[ "$PR_LABELS" == *"Tests"* ]]; then
43-
SECTION="### Changed"
44-
PREFIX="TST"
45-
elif [[ "$PR_LABELS" == *"Docs"* ]]; then
46-
# Only documentation -> Added section
47-
SECTION="### Added"
48-
PREFIX="DOC"
49-
fi
50-
51-
ENTRY="- $PREFIX: $PR_TITLE [#$PR_NUMBER](https://github.com/RocketPy-Team/RocketPy/pull/$PR_NUMBER)"
52-
SECTION_LINE=$(grep -n "^$SECTION$" CHANGELOG.md | head -1 | cut -d: -f1)
53-
54-
sed -i "$((SECTION_LINE + 1))a\\$ENTRY" CHANGELOG.md
30+
# The PR title is untrusted input, so it is read from the environment
31+
# inside Python rather than interpolated into a shell/sed program.
32+
# This avoids both shell injection and sed breaking on special
33+
# characters (\, /, &, newlines) in the title.
34+
python - <<'PY'
35+
import os
36+
37+
labels = os.environ.get("PR_LABELS", "")
38+
title = os.environ["PR_TITLE"]
39+
number = os.environ["PR_NUMBER"]
40+
41+
section, prefix = "### Added", "ENH"
42+
if "Bug" in labels:
43+
section, prefix = "### Fixed", "BUG"
44+
elif "Refactor" in labels:
45+
section, prefix = "### Changed", "MNT"
46+
elif "Docs" in labels and "Git housekeeping" in labels:
47+
section, prefix = "### Changed", "DOC"
48+
elif "Tests" in labels:
49+
section, prefix = "### Changed", "TST"
50+
elif "Docs" in labels:
51+
section, prefix = "### Added", "DOC"
52+
53+
entry = (
54+
f"- {prefix}: {title} "
55+
f"[#{number}](https://github.com/RocketPy-Team/RocketPy/pull/{number})\n"
56+
)
57+
58+
with open("CHANGELOG.md", encoding="utf-8") as handle:
59+
lines = handle.readlines()
60+
61+
for index, line in enumerate(lines):
62+
if line.rstrip("\n") == section:
63+
insert_at = index + 1
64+
# Place the entry at the top of the list, after the single
65+
# blank line that follows the section header.
66+
if insert_at < len(lines) and lines[insert_at].strip() == "":
67+
insert_at += 1
68+
lines.insert(insert_at, entry)
69+
break
70+
else:
71+
raise SystemExit(f"Section {section!r} not found in CHANGELOG.md")
72+
73+
with open("CHANGELOG.md", "w", encoding="utf-8") as handle:
74+
handle.writelines(lines)
75+
PY
5576
5677
- name: Push Changes
5778
run: |

.github/workflows/docs.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ on:
2222
- ".readthedocs.yaml"
2323
- ".github/workflows/docs.yml"
2424

25+
# Least privilege: this workflow only needs to read the repository.
26+
permissions:
27+
contents: read
28+
2529
defaults:
2630
run:
2731
shell: bash
@@ -42,13 +46,13 @@ jobs:
4246
# (reStructuredText, cross-references, toctrees, autodoc API reference).
4347
DOCS_SKIP_EXECUTE: "1"
4448
steps:
45-
- uses: actions/checkout@main
49+
- uses: actions/checkout@v4
4650

4751
- name: Install pandoc (required by nbsphinx)
4852
run: sudo apt-get update && sudo apt-get install -y pandoc
4953

5054
- name: Set up Python ${{ matrix.python-version }}
51-
uses: actions/setup-python@main
55+
uses: actions/setup-python@v5
5256
with:
5357
python-version: ${{ matrix.python-version }}
5458
cache: "pip"
@@ -64,7 +68,7 @@ jobs:
6468
pip install .[all]
6569
6670
- name: Cache Sphinx doctrees
67-
uses: actions/cache@main
71+
uses: actions/cache@v4
6872
with:
6973
path: docs/_build/doctrees
7074
key: sphinx-doctrees-${{ github.ref }}-${{ hashFiles('docs/**', 'rocketpy/**') }}
@@ -96,7 +100,7 @@ jobs:
96100
97101
- name: Upload built HTML
98102
if: always()
99-
uses: actions/upload-artifact@main
103+
uses: actions/upload-artifact@v4
100104
with:
101105
name: docs-html
102106
path: docs/_build/html

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ Attention: The newest changes should be on top -->
5757

5858
### Changed
5959

60-
-
61-
6260
### Deprecated
6361

6462
- MNT: Rename `radius` to `radius_function` in `CylindricalTank` and `SphericalTank`; old `radius=` keyword argument now raises `DeprecationWarning` [#957](https://github.com/RocketPy-Team/RocketPy/pull/957)
@@ -69,6 +67,7 @@ Attention: The newest changes should be on top -->
6967

7068
### Fixed
7169

70+
- FIX: pre-release hardening — bug fixes across the unreleased features [#1047](https://github.com/RocketPy-Team/RocketPy/pull/1047)
7271
- BUG: Remove duplicate controller process; controllers were being invoked twice per time node [#949](https://github.com/RocketPy-Team/RocketPy/pull/949)
7372
- BUG: fix wind heading and direction wraparound interpolation [#974](https://github.com/RocketPy-Team/RocketPy/pull/974)
7473
- BUG: fix NaN in ND linear interpolation outside convex hull (closes [#926](https://github.com/RocketPy-Team/RocketPy/issues/926)) [#969](https://github.com/RocketPy-Team/RocketPy/pull/969)

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,9 @@ Here is just a quick taste of what RocketPy is able to calculate. There are hund
316316
If you want to see the trajectory on Google Earth, RocketPy acn easily export a KML file for you:
317317

318318
```python
319-
test_flight.export_kml(file_name="test_flight.kml")
319+
from rocketpy.simulation import FlightDataExporter
320+
321+
FlightDataExporter(test_flight).export_kml(file_name="test_flight.kml")
320322
```
321323

322324
<img alt="6-DOF Trajectory Plot" src="https://raw.githubusercontent.com/RocketPy-Team/RocketPy/master/docs/static/trajectory-earth.png" width="501">

docs/notebooks/getting_started.ipynb

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,24 +1249,10 @@
12491249
},
12501250
{
12511251
"cell_type": "code",
1252-
"execution_count": 17,
1252+
"execution_count": null,
12531253
"metadata": {},
1254-
"outputs": [
1255-
{
1256-
"name": "stdout",
1257-
"output_type": "stream",
1258-
"text": [
1259-
"File trajectory.kml saved with success!\n"
1260-
]
1261-
}
1262-
],
1263-
"source": [
1264-
"test_flight.export_kml(\n",
1265-
" file_name=\"trajectory.kml\",\n",
1266-
" extrude=True,\n",
1267-
" altitude_mode=\"relative_to_ground\",\n",
1268-
")"
1269-
]
1254+
"outputs": [],
1255+
"source": "from rocketpy.simulation import FlightDataExporter\n\nFlightDataExporter(test_flight).export_kml(\n file_name=\"trajectory.kml\",\n extrude=True,\n altitude_mode=\"relative_to_ground\",\n)"
12701256
},
12711257
{
12721258
"attachments": {},
@@ -1600,4 +1586,4 @@
16001586
},
16011587
"nbformat": 4,
16021588
"nbformat_minor": 2
1603-
}
1589+
}

docs/notebooks/getting_started_colab.ipynb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -591,13 +591,7 @@
591591
"execution_count": null,
592592
"metadata": {},
593593
"outputs": [],
594-
"source": [
595-
"test_flight.export_kml(\n",
596-
" file_name=\"trajectory.kml\",\n",
597-
" extrude=True,\n",
598-
" altitude_mode=\"relative_to_ground\",\n",
599-
")"
600-
]
594+
"source": "from rocketpy.simulation import FlightDataExporter\n\nFlightDataExporter(test_flight).export_kml(\n file_name=\"trajectory.kml\",\n extrude=True,\n altitude_mode=\"relative_to_ground\",\n)"
601595
},
602596
{
603597
"cell_type": "markdown",
@@ -842,4 +836,4 @@
842836
},
843837
"nbformat": 4,
844838
"nbformat_minor": 2
845-
}
839+
}

docs/user/first_simulation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ Use the dedicated exporter class:
589589

590590
.. note::
591591

592-
The legacy method ``Flight.export_kml`` is deprecated. Use
592+
The legacy method ``Flight.export_kml`` was removed in v1.13.0. Use
593593
:meth:`rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_kml`.
594594

595595
Manipulating results
@@ -689,7 +689,7 @@ by not passing any attribute names:
689689

690690
.. note::
691691

692-
The legacy method ``Flight.export_data`` is deprecated. Use
692+
The legacy method ``Flight.export_data`` was removed in v1.13.0. Use
693693
:meth:`rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_data`.
694694

695695
Saving and Storing Plots

rocketpy/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import logging
2-
31
from . import utils
42
from .control import _Controller
53
from .environment import Environment, EnvironmentAnalysis
@@ -72,5 +70,3 @@
7270
StochasticTail,
7371
StochasticTrapezoidalFins,
7472
)
75-
76-
logging.getLogger(__name__).addHandler(logging.NullHandler())

rocketpy/_encoders.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Defines a custom JSON encoder for RocketPy objects."""
22

33
import json
4+
import warnings
45
from datetime import datetime
56
from importlib import import_module
67

@@ -133,7 +134,13 @@ def object_hook(self, obj):
133134
if hash_ is not None:
134135
setattr(new_obj, "__rpy_hash", hash_)
135136
return new_obj
136-
except (ImportError, AttributeError):
137+
except (ImportError, AttributeError) as exc:
138+
warnings.warn(
139+
"Could not reconstruct a RocketPy object from the stored "
140+
f"signature {signature!r}: {exc}. Returning the raw data "
141+
"dictionary instead; the loaded object may be incomplete.",
142+
stacklevel=2,
143+
)
137144
return obj
138145
else:
139146
return obj
@@ -239,10 +246,23 @@ def get_class_from_signature(signature):
239246
type
240247
Class defined by the signature.
241248
"""
242-
module = import_module(signature["module"])
249+
module_name = signature["module"]
250+
name = signature["name"]
251+
252+
# Backward compatibility: the parachute module was moved to the
253+
# ``rocketpy.rocket.parachutes`` subpackage and the old concrete
254+
# ``Parachute`` class became an abstract base, with the hemispherical model
255+
# split out. Remap the legacy signature so ``.rpy`` files saved with older
256+
# versions reconstruct as a concrete ``HemisphericalParachute`` instead of
257+
# silently falling back to a raw dictionary.
258+
if module_name == "rocketpy.rocket.parachute" and name == "Parachute":
259+
module_name = "rocketpy.rocket.parachutes.hemispherical_parachute"
260+
name = "HemisphericalParachute"
261+
262+
module = import_module(module_name)
243263
inner_class = None
244264

245-
for class_ in signature["name"].split("."):
265+
for class_ in name.split("."):
246266
inner_class = getattr(module, class_)
247267

248268
return inner_class

rocketpy/environment/environment.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,6 @@ def __validate_dictionary(self, file, dictionary):
701701
"HRRR",
702702
"HIRESW",
703703
"GEFS",
704-
"ERA5",
705704
"MERRA2",
706705
]
707706
if isinstance(dictionary, str):
@@ -1166,10 +1165,16 @@ def __determine_pressure_conversion_factor(
11661165

11671166
# Auto-detect. Primary source: known-model lookup table.
11681167
# Fallback: units attribute inside the file.
1169-
_hpa_dicts = {"ECMWF", "ECMWF_V0", "ERA5", "MERRA2"}
1170-
_pa_files = {"GFS", "NAM", "RAP", "HRRR", "AIGFS", "HIRESW", "GEFS"}
1168+
# THREDDS (UCAR) models expose pressure on the 'isobaric' coordinate in
1169+
# Pa; NOMADS-GrADS models (GEFS, HIRESW) expose it on the 'lev'
1170+
# coordinate in hPa/millibars and must be scaled by 100.
1171+
_hpa_dicts = {"ECMWF", "ECMWF_V0", "MERRA2"}
1172+
_hpa_files = {"GEFS", "HIRESW"}
1173+
_pa_files = {"GFS", "NAM", "RAP", "HRRR", "AIGFS"}
11711174
if input_dict in _hpa_dicts or input_file in _hpa_dicts:
11721175
return 100
1176+
if input_file in _hpa_files:
1177+
return 100
11731178
if input_file in _pa_files:
11741179
return 1
11751180
return None
@@ -1219,7 +1224,7 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
12191224
- ``"windy"``: ignored.
12201225
- ``"forecast"``, ``"reanalysis"`` and ``"ensemble"``: local
12211226
dictionary, or one of the built-in mappings (e.g. ``"ECMWF"``,
1222-
``"GFS"``, ``"ERA5"``, ``"RAP"``, ``"HRRR"``, etc.) corresponding
1227+
``"GFS"``, ``"MERRA2"``, ``"RAP"``, ``"HRRR"``, etc.) corresponding
12231228
to the file structure.
12241229
pressure : float, string, array, callable, optional
12251230
This defines the atmospheric pressure profile. Should be given if

0 commit comments

Comments
 (0)