diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 930141225..b032e643e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,61 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - id: debug-statements -- repo: https://github.com/psf/black - rev: 22.3.0 + - id: check-symlinks + - id: destroyed-symlinks + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-ast + - id: check-added-large-files + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: detect-private-key + - id: debug-statements +# - repo: https://github.com/codespell-project/codespell +# rev: v2.2.6 +# exclude: ^(src/common)|(src/emucore)|(src/environment)|(src/games) +# hooks: +# - id: codespell +# args: +# - --ignore-words-list= + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 hooks: - - id: black + - id: flake8 + args: +# - '--per-file-ignores=' + - --ignore=E203,W503,E741 + - --max-complexity=30 + - --max-line-length=456 + - --show-source + - --statistics + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: ["--py38-plus"] + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/python/black + rev: 23.12.1 + hooks: + - id: black +# - repo: https://github.com/pycqa/pydocstyle +# rev: 6.3.0 +# hooks: +# - id: pydocstyle +## exclude: ^ +# args: +# - --source +# - --explain +# - --convention=google +# additional_dependencies: ["tomli"] diff --git a/examples/python-interface/python_example.py b/examples/python-interface/python_example.py index c11146ed3..b2fb928e2 100755 --- a/examples/python-interface/python_example.py +++ b/examples/python-interface/python_example.py @@ -6,7 +6,8 @@ # ALE provided in examples/sharedLibraryInterfaceExample.cpp import sys from random import randrange -from ale_py import ALEInterface, SDL_SUPPORT + +from ale_py import SDL_SUPPORT, ALEInterface if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} rom_file") diff --git a/examples/python-interface/python_example_with_modes.py b/examples/python-interface/python_example_with_modes.py index d9b656c40..238495a18 100755 --- a/examples/python-interface/python_example_with_modes.py +++ b/examples/python-interface/python_example_with_modes.py @@ -6,7 +6,8 @@ # ALE provided in doc/examples/sharedLibraryInterfaceWithModesExample.cpp import sys from random import randrange -from ale_py import ALEInterface, SDL_SUPPORT + +from ale_py import SDL_SUPPORT, ALEInterface if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} rom_file") @@ -40,7 +41,6 @@ # Play one episode in each mode and in each difficulty for mode in avail_modes: for diff in avail_diff: - ale.setDifficulty(diff) ale.setMode(mode) ale.reset_game() diff --git a/examples/python-rom-package/roms/__init__.py b/examples/python-rom-package/roms/__init__.py index 95436eefd..c6231af0c 100644 --- a/examples/python-rom-package/roms/__init__.py +++ b/examples/python-rom-package/roms/__init__.py @@ -1,5 +1,5 @@ -import sys import pathlib +import sys if sys.version_info >= (3, 9): import importlib.resources as resources diff --git a/setup.py b/setup.py index 87bc7c82e..7307c30bf 100644 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ def parse_version(version_file): semver_regex = r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?" semver_prog = re.compile(semver_regex) - with open(version_file, "r") as fp: + with open(version_file) as fp: version = fp.read().strip() assert semver_prog.match(version) is not None diff --git a/src/python/__init__.py b/src/python/__init__.py index d7e4001f9..c56f9cc99 100644 --- a/src/python/__init__.py +++ b/src/python/__init__.py @@ -45,6 +45,12 @@ __version__ = "unknown" # Import native shared library -from ale_py._ale_py import SDL_SUPPORT, Action, ALEInterface, ALEState, LoggerMode +from ale_py._ale_py import ( # noqa: E402 + SDL_SUPPORT, + Action, + ALEInterface, + ALEState, + LoggerMode, +) __all__ = ["Action", "ALEInterface", "ALEState", "LoggerMode", "SDL_SUPPORT"] diff --git a/src/python/gym_env.py b/src/python/gym_env.py index efc7875f8..e541fc7a7 100644 --- a/src/python/gym_env.py +++ b/src/python/gym_env.py @@ -1,15 +1,14 @@ from __future__ import annotations import sys -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Sequence import ale_py import ale_py.roms as roms import ale_py.roms.utils as rom_utils -import numpy as np - import gym -from gym import error, spaces, utils, logger +import numpy as np +from gym import error, logger, spaces, utils if sys.version_info < (3, 11): from typing_extensions import NotRequired, TypedDict @@ -36,14 +35,14 @@ class AtariEnv(gym.Env, utils.EzPickle): def __init__( self, game: str = "pong", - mode: Optional[int] = None, - difficulty: Optional[int] = None, + mode: int | None = None, + difficulty: int | None = None, obs_type: str = "rgb", - frameskip: Union[Tuple[int, int], int] = 4, + frameskip: tuple[int, int] | int = 4, repeat_action_probability: float = 0.25, full_action_space: bool = False, - max_num_frames_per_episode: Optional[int] = None, - render_mode: Optional[str] = None, + max_num_frames_per_episode: int | None = None, + render_mode: str | None = None, ) -> None: """ Initialize the ALE for Gym. @@ -59,7 +58,7 @@ def __init__( repeat_action_probability: int => Probability to repeat actions, see Machado et al., 2018 full_action_space: bool => Use full action space? - max_num_frames_per_episode: int => Max number of frame per epsiode. + max_num_frames_per_episode: int => Max number of frame per episode. Once `max_num_frames_per_episode` is reached the episode is truncated. render_mode: str => One of { 'human', 'rgb_array' }. @@ -102,11 +101,11 @@ def __init__( ) elif isinstance(frameskip, tuple) and frameskip[0] > frameskip[1]: raise error.Error( - f"Invalid stochastic frameskip, lower bound is greater than upper bound." + "Invalid stochastic frameskip, lower bound is greater than upper bound." ) elif isinstance(frameskip, tuple) and frameskip[0] <= 0: raise error.Error( - f"Invalid stochastic frameskip lower bound is greater than upper bound." + "Invalid stochastic frameskip lower bound is greater than upper bound." ) if render_mode is not None and render_mode not in {"rgb_array", "human"}: @@ -181,7 +180,7 @@ def __init__( else: raise error.Error(f"Unrecognized observation type: {self._obs_type}") - def seed(self, seed: Optional[int] = None) -> Tuple[int, int]: + def seed(self, seed: int | None = None) -> tuple[int, int]: """ Seeds both the internal numpy rng for stochastic frame skip as well as the ALE RNG. @@ -225,7 +224,7 @@ def seed(self, seed: Optional[int] = None) -> Tuple[int, int]: def step( self, action_ind: int, - ) -> Tuple[np.ndarray, float, bool, bool, AtariEnvStepMetadata]: + ) -> tuple[np.ndarray, float, bool, bool, AtariEnvStepMetadata]: """ Perform one agent step, i.e., repeats `action` frameskip # of steps. @@ -263,9 +262,9 @@ def step( def reset( self, *, - seed: Optional[int] = None, - options: Optional[Dict[str, Any]] = None, - ) -> Tuple[np.ndarray, AtariEnvStepMetadata]: + seed: int | None = None, + options: dict[str, Any] | None = None, + ) -> tuple[np.ndarray, AtariEnvStepMetadata]: """ Resets environment and returns initial observation. """ @@ -307,7 +306,7 @@ def render(self) -> Any: def _get_obs(self) -> np.ndarray: """ - Retreives the current observation. + Retrieves the current observation. This is dependent on `self._obs_type`. """ if self._obs_type == "ram": @@ -326,7 +325,7 @@ def _get_info(self) -> AtariEnvStepMetadata: "frame_number": self.ale.getFrameNumber(), } - def get_keys_to_action(self) -> Dict[Tuple[int], ale_py.Action]: + def get_keys_to_action(self) -> dict[tuple[int], ale_py.Action]: """ Return keymapping -> actions for human play. """ @@ -369,7 +368,7 @@ def get_keys_to_action(self) -> Dict[Tuple[int], ale_py.Action]: ) ) - def get_action_meanings(self) -> List[str]: + def get_action_meanings(self) -> list[str]: """ Return the meaning of each integer action. """ diff --git a/src/python/gym_registration.py b/src/python/gym_registration.py index 1e5022f35..559097e27 100644 --- a/src/python/gym_registration.py +++ b/src/python/gym_registration.py @@ -1,22 +1,21 @@ from __future__ import annotations from collections import defaultdict -from typing import Any, Callable, Mapping, NamedTuple, Sequence, Text, Union +from typing import Any, Callable, Mapping, NamedTuple, Sequence import ale_py.roms as roms from ale_py.roms import utils as rom_utils - from gym.envs.registration import register class GymFlavour(NamedTuple): suffix: str - kwargs: Union[Mapping[Text, Any], Callable[[str], Mapping[Text, Any]]] + kwargs: Mapping[str, Any] | Callable[[str], Mapping[str, Any]] class GymConfig(NamedTuple): version: str - kwargs: Mapping[Text, Any] + kwargs: Mapping[str, Any] flavours: Sequence[GymFlavour] diff --git a/src/python/gymnasium_env.py b/src/python/gymnasium_env.py index e2b56ce4f..1222e39ae 100644 --- a/src/python/gymnasium_env.py +++ b/src/python/gymnasium_env.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from typing import Any, Literal, Optional, Sequence, Union +from typing import Any, Literal, Sequence import ale_py import gymnasium @@ -36,14 +36,14 @@ class AtariEnv(gymnasium.Env, utils.EzPickle): def __init__( self, game: str = "pong", - mode: Optional[int] = None, - difficulty: Optional[int] = None, + mode: int | None = None, + difficulty: int | None = None, obs_type: Literal["rgb", "grayscale", "ram"] = "rgb", - frameskip: Union[tuple[int, int], int] = 4, + frameskip: tuple[int, int] | int = 4, repeat_action_probability: float = 0.25, full_action_space: bool = False, - max_num_frames_per_episode: Optional[int] = None, - render_mode: Optional[Literal["human", "rgb_array"]] = None, + max_num_frames_per_episode: int | None = None, + render_mode: Literal["human", "rgb_array"] | None = None, ) -> None: """ Initialize the ALE for Gymnasium. @@ -59,7 +59,7 @@ def __init__( repeat_action_probability: int => Probability to repeat actions, see Machado et al., 2018 full_action_space: bool => Use full action space? - max_num_frames_per_episode: int => Max number of frame per epsiode. + max_num_frames_per_episode: int => Max number of frame per episode. Once `max_num_frames_per_episode` is reached the episode is truncated. render_mode: str => One of { 'human', 'rgb_array' }. @@ -97,11 +97,11 @@ def __init__( ) elif isinstance(frameskip, tuple) and frameskip[0] > frameskip[1]: raise error.Error( - f"Invalid stochastic frameskip, lower bound is greater than upper bound." + "Invalid stochastic frameskip, lower bound is greater than upper bound." ) elif isinstance(frameskip, tuple) and frameskip[0] <= 0: raise error.Error( - f"Invalid stochastic frameskip lower bound is greater than upper bound." + "Invalid stochastic frameskip lower bound is greater than upper bound." ) if render_mode is not None and render_mode not in {"rgb_array", "human"}: @@ -177,7 +177,7 @@ def __init__( else: raise error.Error(f"Unrecognized observation type: {self._obs_type}") - def seed_game(self, seed: Optional[int] = None) -> tuple[int, int]: + def seed_game(self, seed: int | None = None) -> tuple[int, int]: """Seeds the internal and ALE RNG.""" ss = np.random.SeedSequence(seed) np_seed, ale_seed = ss.generate_state(n_words=2) @@ -232,8 +232,8 @@ def step( # pyright: ignore[reportIncompatibleMethodOverride] def reset( # pyright: ignore[reportIncompatibleMethodOverride] self, *, - seed: Optional[int] = None, - options: Optional[dict[str, Any]] = None, + seed: int | None = None, + options: dict[str, Any] | None = None, ) -> tuple[np.ndarray, AtariEnvStepMetadata]: """Resets environment and returns initial observation.""" # sets the seeds if it's specified for both ALE and frameskip np @@ -252,7 +252,7 @@ def reset( # pyright: ignore[reportIncompatibleMethodOverride] return obs, info - def render(self) -> Optional[np.ndarray]: + def render(self) -> np.ndarray | None: """ Render is not supported by ALE. We use a paradigm similar to Gym3 which allows you to specify `render_mode` during construction. @@ -274,7 +274,7 @@ def render(self) -> Optional[np.ndarray]: def _get_obs(self) -> np.ndarray: """ - Retreives the current observation. + Retrieves the current observation. This is dependent on `self._obs_type`. """ if self._obs_type == "ram": diff --git a/src/python/gymnasium_registration.py b/src/python/gymnasium_registration.py index 5021d4289..cf644b27e 100644 --- a/src/python/gymnasium_registration.py +++ b/src/python/gymnasium_registration.py @@ -1,22 +1,21 @@ from __future__ import annotations from collections import defaultdict -from typing import Any, Callable, Mapping, NamedTuple, Sequence, Text, Union +from typing import Any, Callable, Mapping, NamedTuple, Sequence import ale_py.roms as roms -from ale_py.roms import utils as rom_utils - import gymnasium +from ale_py.roms import utils as rom_utils class GymFlavour(NamedTuple): suffix: str - kwargs: Union[Mapping[Text, Any], Callable[[str], Mapping[Text, Any]]] + kwargs: Mapping[str, Any] | Callable[[str], Mapping[str, Any]] class GymConfig(NamedTuple): version: str - kwargs: Mapping[Text, Any] + kwargs: Mapping[str, Any] flavours: Sequence[GymFlavour] diff --git a/src/python/roms/__init__.py b/src/python/roms/__init__.py index cf2e8b1ee..36ad22670 100644 --- a/src/python/roms/__init__.py +++ b/src/python/roms/__init__.py @@ -97,7 +97,7 @@ def __dir__() -> List[str]: md5s = resources.files(__name__).joinpath("md5.txt") if not md5s.exists(): raise FileNotFoundError( - f"ROM md5 resource couldn't be found. " + "ROM md5 resource couldn't be found. " "Are you running from a development environment? " ) with md5s.open() as fp: diff --git a/src/python/roms/plugins.py b/src/python/roms/plugins.py index 8dc4e9f06..b2fd25f55 100644 --- a/src/python/roms/plugins.py +++ b/src/python/roms/plugins.py @@ -5,8 +5,6 @@ import warnings from typing import Optional, Union -from ale_py.roms import utils - # pylint: disable=g-import-not-at-top if sys.version_info < (3, 10): import importlib_metadata as metadata diff --git a/src/python/scripts/import_roms.py b/src/python/scripts/import_roms.py index 14a71e266..33191841f 100644 --- a/src/python/scripts/import_roms.py +++ b/src/python/scripts/import_roms.py @@ -41,7 +41,7 @@ def import_roms( print(f"\033[92m{'[SUPPORTED]': <15}\033[0m {rom: >20} {identifier: >30}") print("\n") - # Print unsuported + # Print unsupported for path in unsupported: identifier = str(path) if pkg is None else f"{pkg}/{path.name}" print(f"\033[91m{'[NOT SUPPORTED]': <15}\033[0m {'': >20} {identifier: >30}") diff --git a/tests/conftest.py b/tests/conftest.py index f57228a2c..7ce292608 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os + import pytest @@ -13,6 +14,3 @@ def __getitem__(self, file_name): @pytest.fixture(scope="module") def resources(): return Resources(os.path.abspath(os.path.dirname(__file__))) - - -from fixtures import * diff --git a/tests/python/gym/test_gym_interface.py b/tests/python/gym/test_gym_interface.py index f25a58f81..b19b390fa 100644 --- a/tests/python/gym/test_gym_interface.py +++ b/tests/python/gym/test_gym_interface.py @@ -1,19 +1,15 @@ # fmt: off -import pytest - -pytest.importorskip("gym") - from itertools import product from unittest.mock import patch import numpy as np +import pytest from ale_py.gym_env import AtariEnv from ale_py.gym_registration import ( _register_gym_configs, register_gym_envs, register_legacy_gym_envs, ) - from gym import error, spaces from gym.core import Env from gym.envs.registration import registry diff --git a/tests/python/gym/test_legacy_registration.py b/tests/python/gym/test_legacy_registration.py index 0e42bc02d..0b53fe08b 100644 --- a/tests/python/gym/test_legacy_registration.py +++ b/tests/python/gym/test_legacy_registration.py @@ -1,7 +1,3 @@ -import pytest - -pytest.importorskip("gym") - from itertools import product from gym.envs.registration import registry @@ -121,7 +117,7 @@ def test_legacy_env_specs(): if "NoFrameskip" in spec: assert kwargs["frameskip"] == 1 - steps = 300000 if "SpaceInvaders" in spec else 400000 + # steps = 300000 if "SpaceInvaders" in spec else 400000 elif "Deterministic" in spec: assert isinstance(kwargs["frameskip"], int) frameskip = 3 if "SpaceInvaders" in spec else 4 diff --git a/tests/python/test_python_interface.py b/tests/python/test_python_interface.py index a2317d0ea..6cbf3d475 100644 --- a/tests/python/test_python_interface.py +++ b/tests/python/test_python_interface.py @@ -225,7 +225,7 @@ def test_save_screen_png(tetris): def test_is_rom_supported(ale, test_rom_path, random_rom_path): assert ale.isSupportedROM(test_rom_path) assert ale.isSupportedROM(random_rom_path) is None - with pytest.raises(RuntimeError) as exc_info: + with pytest.raises(RuntimeError): ale.isSupportedROM("notfound") @@ -380,7 +380,7 @@ def test_state_pickle(tetris): state = tetris.cloneState() file = os.path.join(tempfile.gettempdir(), "ale-state.p") with open(file, "wb") as fp: - data = pickle.dump(state, fp) + pickle.dump(state, fp) tetris.reset_game() assert tetris.cloneState() != state