Skip to content

Commit d29756c

Browse files
authored
Add Python 3.13 support, drop Python 3.9 (#501)
* Drop Python 3.9 support * Ignore unused variables * Autofixes for Python 3.10 * Use zip strict * Reformat and fix mypy issues
1 parent ab4aadb commit d29756c

File tree

14 files changed

+90
-76
lines changed

14 files changed

+90
-76
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ jobs:
2020
runs-on: ubuntu-latest
2121
strategy:
2222
matrix:
23-
python-version: ["3.9", "3.10", "3.11", "3.12"]
23+
python-version: ["3.10", "3.11", "3.12", "3.13"]
2424
include:
2525
# Default version
2626
- gymnasium-version: "1.0.0"
2727
# Add a new config to test gym<1.0
2828
- python-version: "3.10"
2929
gymnasium-version: "0.29.1"
3030
steps:
31-
- uses: actions/checkout@v3
31+
- uses: actions/checkout@v6
3232
with:
3333
submodules: true
3434
- name: Set up Python ${{ matrix.python-version }}
35-
uses: actions/setup-python@v4
35+
uses: actions/setup-python@v6
3636
with:
3737
python-version: ${{ matrix.python-version }}
3838
- name: Install dependencies
@@ -42,7 +42,8 @@ jobs:
4242
pip install uv
4343
# cpu version of pytorch
4444
# See https://github.com/astral-sh/uv/issues/1497
45-
uv pip install --system torch==2.4.1+cpu --index https://download.pytorch.org/whl/cpu
45+
# Need Pytorch 2.9+ for Python 3.13
46+
uv pip install --system torch==2.9.1+cpu --index https://download.pytorch.org/whl/cpu
4647
# Install full requirements (for additional envs and test tools)
4748
uv pip install --system -r requirements.txt
4849
# Use headless version

.github/workflows/trained_agents.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ jobs:
2020
runs-on: ubuntu-latest
2121
strategy:
2222
matrix:
23-
python-version: ["3.9", "3.10", "3.11", "3.12"]
23+
python-version: ["3.10", "3.11", "3.12", "3.13"]
2424
include:
2525
# Default version
2626
- gymnasium-version: "1.0.0"
2727
# Add a new config to test gym<1.0
2828
- python-version: "3.10"
2929
gymnasium-version: "0.29.1"
3030
steps:
31-
- uses: actions/checkout@v3
31+
- uses: actions/checkout@v6
3232
with:
3333
submodules: true
3434
- name: Set up Python ${{ matrix.python-version }}
35-
uses: actions/setup-python@v4
35+
uses: actions/setup-python@v6
3636
with:
3737
python-version: ${{ matrix.python-version }}
3838
- name: Install dependencies
@@ -43,7 +43,8 @@ jobs:
4343
pip install uv
4444
# cpu version of pytorch
4545
# See https://github.com/astral-sh/uv/issues/1497
46-
uv pip install --system torch==2.4.1+cpu --index https://download.pytorch.org/whl/cpu
46+
# Need Pytorch 2.9+ for Python 3.13
47+
uv pip install --system torch==2.9.1+cpu --index https://download.pytorch.org/whl/cpu
4748
# Install full requirements (for additional envs and test tools)
4849
uv pip install --system -r requirements.txt
4950
# Use headless version

CHANGELOG.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## Release 2.8.0a0 (WIP)
2+
3+
### Breaking Changes
4+
- Upgraded to SB3 >= 2.8.0
5+
- Removed support for Python 3.9, please upgrade to Python >= 3.10
6+
- Set ``strict=True`` for every call to ``zip(...)``
7+
8+
### New Features
9+
- Added official support for Python 3.13
10+
11+
### Bug fixes
12+
13+
### Documentation
14+
15+
### Other
16+
17+
118
## Release 2.7.0 (2025-07-25)
219

320
### Breaking Changes
@@ -14,10 +31,6 @@
1431
- Use `ConstantSchedule`, and `SimpleLinearSchedule` instead of `constant_fn` and `linear_schedule`
1532
- Fixed `CarRacing-v3` hyperparameters for newer Gymnasium version
1633

17-
### Documentation
18-
19-
### Other
20-
2134
## Release 2.6.0 (2025-03-24)
2235

2336
### Breaking Changes

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[tool.ruff]
22
# Same as Black.
33
line-length = 127
4-
# Assume Python 3.9
5-
target-version = "py39"
4+
# Assume Python 3.10
5+
target-version = "py310"
66

77
[tool.ruff.lint]
88
# See https://beta.ruff.rs/docs/rules/

rl_zoo3/callbacks.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from copy import deepcopy
55
from functools import wraps
66
from threading import Thread
7-
from typing import Optional, Union
87

98
import optuna
109
from sb3_contrib import TQC
@@ -27,8 +26,8 @@ def __init__(
2726
eval_freq: int = 10000,
2827
deterministic: bool = True,
2928
verbose: int = 0,
30-
best_model_save_path: Optional[str] = None,
31-
log_path: Optional[str] = None,
29+
best_model_save_path: str | None = None,
30+
log_path: str | None = None,
3231
) -> None:
3332
super().__init__(
3433
eval_env=eval_env,
@@ -67,7 +66,7 @@ class SaveVecNormalizeCallback(BaseCallback):
6766
only one file will be kept.
6867
"""
6968

70-
def __init__(self, save_freq: int, save_path: str, name_prefix: Optional[str] = None, verbose: int = 0):
69+
def __init__(self, save_freq: int, save_path: str, name_prefix: str | None = None, verbose: int = 0):
7170
super().__init__(verbose)
7271
self.save_freq = save_freq
7372
self.save_path = save_path
@@ -116,10 +115,10 @@ def __init__(self, gradient_steps: int = 100, verbose: int = 0, sleep_time: floa
116115
super().__init__(verbose)
117116
self.batch_size = 0
118117
self._model_ready = True
119-
self._model: Union[SAC, TQC]
118+
self._model: SAC | TQC
120119
self.gradient_steps = gradient_steps
121120
self.process: Thread
122-
self.model_class: Union[type[SAC], type[TQC]]
121+
self.model_class: type[SAC] | type[TQC]
123122
self.sleep_time = sleep_time
124123

125124
def _init_callback(self) -> None:

rl_zoo3/exp_manager.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import time
77
import warnings
88
from collections import OrderedDict
9+
from collections.abc import Callable
910
from pathlib import Path
1011
from pprint import pprint
11-
from typing import Any, Callable, Optional, Union
12+
from typing import Any
1213

1314
import gymnasium as gym
1415
import numpy as np
@@ -69,7 +70,7 @@ class ExperimentManager:
6970
"""
7071

7172
# For special VecEnv like Brax, IsaacLab, ...
72-
default_vec_env_cls: Optional[type[VecEnv]] = None
73+
default_vec_env_cls: type[VecEnv] | None = None
7374

7475
def __init__(
7576
self,
@@ -82,19 +83,19 @@ def __init__(
8283
eval_freq: int = 10000,
8384
n_eval_episodes: int = 5,
8485
save_freq: int = -1,
85-
hyperparams: Optional[dict[str, Any]] = None,
86-
env_kwargs: Optional[dict[str, Any]] = None,
87-
eval_env_kwargs: Optional[dict[str, Any]] = None,
86+
hyperparams: dict[str, Any] | None = None,
87+
env_kwargs: dict[str, Any] | None = None,
88+
eval_env_kwargs: dict[str, Any] | None = None,
8889
trained_agent: str = "",
8990
optimize_hyperparameters: bool = False,
90-
storage: Optional[str] = None,
91-
study_name: Optional[str] = None,
91+
storage: str | None = None,
92+
study_name: str | None = None,
9293
n_trials: int = 1,
93-
max_total_trials: Optional[int] = None,
94+
max_total_trials: int | None = None,
9495
n_jobs: int = 1,
9596
sampler: str = "tpe",
9697
pruner: str = "median",
97-
optimization_log_path: Optional[str] = None,
98+
optimization_log_path: str | None = None,
9899
n_startup_trials: int = 0,
99100
n_evaluations: int = 1,
100101
truncate_last_trajectory: bool = False,
@@ -106,10 +107,10 @@ def __init__(
106107
vec_env_type: str = "dummy",
107108
n_eval_envs: int = 1,
108109
no_optim_plots: bool = False,
109-
device: Union[th.device, str] = "auto",
110-
config: Optional[str] = None,
110+
device: th.device | str = "auto",
111+
config: str | None = None,
111112
show_progress: bool = False,
112-
trial_id: Optional[int] = None,
113+
trial_id: int | None = None,
113114
):
114115
super().__init__()
115116
self.algo = algo
@@ -128,7 +129,7 @@ def __init__(
128129
self.n_timesteps = n_timesteps
129130
self.normalize = False
130131
self.normalize_kwargs: dict[str, Any] = {}
131-
self.env_wrapper: Optional[Callable] = None
132+
self.env_wrapper: Callable | None = None
132133
self.frame_stack = None
133134
self.seed = seed
134135
self.optimization_log_path = optimization_log_path
@@ -138,7 +139,7 @@ def __init__(
138139
if self.default_vec_env_cls is not None:
139140
self.vec_env_class = self.default_vec_env_cls
140141

141-
self.vec_env_wrapper: Optional[Callable] = None
142+
self.vec_env_wrapper: Callable | None = None
142143

143144
self.vec_env_kwargs: dict[str, Any] = {}
144145
# self.vec_env_kwargs = {} if vec_env_type == "dummy" else {"start_method": "fork"}
@@ -197,7 +198,7 @@ def __init__(
197198
)
198199
self.params_path = f"{self.save_path}/{self.env_name}"
199200

200-
def setup_experiment(self) -> Optional[tuple[BaseAlgorithm, dict[str, Any]]]:
201+
def setup_experiment(self) -> tuple[BaseAlgorithm, dict[str, Any]] | None:
201202
"""
202203
Read hyperparameters, pre-process them (create schedules, wrappers, callbacks, action noise objects)
203204
create the environment and possibly the model.
@@ -361,12 +362,10 @@ def read_hyperparameters(self) -> tuple[dict[str, Any], dict[str, Any]]:
361362

362363
return hyperparams, saved_hyperparams
363364

364-
def load_trial(
365-
self, storage: str, study_name: str, trial_id: Optional[int] = None, convert: bool = True
366-
) -> dict[str, Any]:
365+
def load_trial(self, storage: str, study_name: str, trial_id: int | None = None, convert: bool = True) -> dict[str, Any]:
367366

368367
if storage.endswith(".log"):
369-
optuna_storage = optuna.storages.JournalStorage(optuna.storages.journal.JournalFileBackend(storage))
368+
optuna_storage = optuna.storages.JournalStorage(optuna.storages.journal.JournalFileBackend(storage)) # type: ignore[attr-defined]
370369
else:
371370
optuna_storage = storage # type: ignore[assignment]
372371
study = optuna.load_study(storage=optuna_storage, study_name=study_name)
@@ -386,7 +385,7 @@ def _preprocess_schedules(hyperparams: dict[str, Any]) -> dict[str, Any]:
386385
if key not in hyperparams:
387386
continue
388387
if isinstance(hyperparams[key], str):
389-
schedule, initial_value = hyperparams[key].split("_")
388+
_schedule, initial_value = hyperparams[key].split("_")
390389
initial_value = float(initial_value)
391390
hyperparams[key] = SimpleLinearSchedule(initial_value)
392391
elif isinstance(hyperparams[key], (float, int)):
@@ -424,7 +423,7 @@ def _preprocess_normalization(self, hyperparams: dict[str, Any]) -> dict[str, An
424423

425424
def _preprocess_hyperparams( # noqa: C901
426425
self, hyperparams: dict[str, Any]
427-
) -> tuple[dict[str, Any], Optional[Callable], list[BaseCallback], Optional[Callable]]:
426+
) -> tuple[dict[str, Any], Callable | None, list[BaseCallback], Callable | None]:
428427
self.n_envs = hyperparams.get("n_envs", 1)
429428

430429
if self.verbose > 0:
@@ -891,7 +890,7 @@ def hyperparameters_optimization(self) -> None:
891890
# Create folder if it doesn't exist
892891
Path(storage).parent.mkdir(parents=True, exist_ok=True)
893892
storage = optuna.storages.JournalStorage( # type: ignore[assignment]
894-
optuna.storages.journal.JournalFileBackend(storage),
893+
optuna.storages.journal.JournalFileBackend(storage), # type: ignore[attr-defined]
895894
)
896895

897896
if self.verbose > 0:

rl_zoo3/import_envs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Callable, Optional
1+
from collections.abc import Callable
2+
from typing import Optional
23

34
import gymnasium as gym
45
from gymnasium.envs.registration import register, register_envs
@@ -55,8 +56,8 @@
5556

5657

5758
# Register no vel envs
58-
def create_no_vel_env(env_id: str) -> Callable[[Optional[str]], gym.Env]:
59-
def make_env(render_mode: Optional[str] = None) -> gym.Env:
59+
def create_no_vel_env(env_id: str) -> Callable[[str | None], gym.Env]:
60+
def make_env(render_mode: str | None = None) -> gym.Env:
6061
env = gym.make(env_id, render_mode=render_mode)
6162
env = MaskVelocityWrapper(env)
6263
return env

rl_zoo3/load_from_hub.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import shutil
44
import zipfile
55
from pathlib import Path
6-
from typing import Optional
76

87
from huggingface_sb3 import EnvironmentName, ModelName, ModelRepoId, load_from_hub
98
from requests.exceptions import HTTPError
@@ -17,7 +16,7 @@ def download_from_hub(
1716
exp_id: int,
1817
folder: str,
1918
organization: str,
20-
repo_name: Optional[str] = None,
19+
repo_name: str | None = None,
2120
force: bool = False,
2221
) -> None:
2322
"""

rl_zoo3/plots/plot_from_file.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def plot_from_file(): # noqa: C901
109109

110110
labels = {key: key for key in keys}
111111
if args.labels is not None:
112-
for key, label in zip(keys, args.labels):
112+
for key, label in zip(keys, args.labels, strict=True):
113113
labels[key] = label
114114

115115
if not args.skip_timesteps:
@@ -234,7 +234,7 @@ def plot_from_file(): # noqa: C901
234234
confidence_interval_size=args.ci_size, # Coverage of confidence interval. Defaults to 95%.
235235
)
236236

237-
fig, axes = plot_utils.plot_interval_estimates(
237+
fig, _axes = plot_utils.plot_interval_estimates(
238238
aggregate_scores,
239239
aggregate_interval_estimates,
240240
metric_names=["Median", "IQM", "Mean", "Optimality Gap"],
@@ -266,7 +266,7 @@ def plot_from_file(): # noqa: C901
266266
score_distributions,
267267
normalized_score_thresholds,
268268
performance_profile_cis=score_distributions_cis,
269-
colors=dict(zip(algorithms, seaborn.color_palette("colorblind"))),
269+
colors=dict(zip(algorithms, seaborn.color_palette("colorblind"), strict=False)),
270270
xlabel=r"Normalized Score $(\tau)$",
271271
ax=ax,
272272
)
@@ -350,7 +350,7 @@ def plot_from_file(): # noqa: C901
350350

351351
ax = seaborn.barplot(x="Environment", y="Score", hue="Method", data=data_frame)
352352
# Custom legend title
353-
handles, labels_legend = ax.get_legend_handles_labels()
353+
_handles, _labels_legend = ax.get_legend_handles_labels()
354354
# ax.legend(handles=handles, labels=labels_legend, title=r"$log \sigma$", loc=args.legend_loc)
355355
# ax.legend(handles=handles, labels=labels_legend, title="Network Architecture", loc=args.legend_loc)
356356
# ax.legend(handles=handles, labels=labels_legend, title="Interval", loc=args.legend_loc)

rl_zoo3/push_to_hub.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from copy import deepcopy
77
from pathlib import Path
88
from pprint import pformat
9-
from typing import Any, Optional
9+
from typing import Any
1010

1111
import torch as th
1212
import yaml
@@ -139,7 +139,7 @@ def package_to_hub(
139139
commit_message: str,
140140
is_deterministic: bool = True,
141141
n_eval_episodes=10,
142-
token: Optional[str] = None,
142+
token: str | None = None,
143143
local_repo_path="hub",
144144
video_length=1000,
145145
generate_video: bool = False,

0 commit comments

Comments
 (0)