diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0d40764..5704994 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,6 +24,7 @@ - [ ] My change requires a change to the documentation. - [ ] I have updated the tests accordingly (*required for a bug fix or a new feature*). - [ ] I have updated the documentation accordingly. -- [ ] I have ensured `pytest` and `pytype` both pass. +- [ ] I have checked the codestyle using `make lint` +- [ ] I have ensured `make pytest` and `make type` both pass. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5251d52 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + # Skip CI if [ci skip] in the commit message + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7] # 3.8 not supported yet by pytype + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # cpu version of pytorch + pip install torch==1.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html + pip install .[extra,tests,docs] + # Use headless version + pip install opencv-python-headless + - name: Type check + run: | + make type + - name: Lint with flake8 + run: | + make lint + - name: Test with pytest + run: | + make pytest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98b8b26..ff56c13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,6 +99,12 @@ Type checking with `pytype`: make type ``` +Codestyle check with `flake8`: + +``` +make lint +``` + Build the documentation: ``` diff --git a/Makefile b/Makefile index e846cf0..e874ee7 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,13 @@ pytest: type: pytype +lint: + # stop the build if there are Python syntax errors or undefined names + # see https://lintlyci.github.io/Flake8Rules/ + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. + flake8 . --count --exit-zero --statistics + doc: cd docs && make html @@ -15,7 +22,7 @@ spelling: clean: cd docs && make clean -.PHONY: clean spelling doc +.PHONY: clean spelling doc lint # Build docker images # If you do export RELEASE=True, it will also push them diff --git a/docs/misc/changelog.rst b/docs/misc/changelog.rst index c91bbf0..32fcaa0 100644 --- a/docs/misc/changelog.rst +++ b/docs/misc/changelog.rst @@ -34,6 +34,8 @@ Others: - Sync ``VecEnvs`` with Stable-Baselines - Update requirement: ``gym>=0.17`` - Added ``.readthedoc.yml`` file +- Added ``flake8`` and ``make lint`` command +- Added Github workflow Documentation: ^^^^^^^^^^^^^^ diff --git a/setup.cfg b/setup.cfg index 21bdaec..41e1c32 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,3 +19,30 @@ filterwarnings = [pytype] inputs = stable_baselines3 + +[flake8] +ignore = W503,W504 # line breaks before and after binary operators +# Ignore import not used when aliases are defined +per-file-ignores = + ./stable_baselines3/__init__.py:F401 + ./stable_baselines3/common/__init__.py:F401 + ./stable_baselines3/a2c/__init__.py:F401 + ./stable_baselines3/ppo/__init__.py:F401 + ./stable_baselines3/sac/__init__.py:F401 + ./stable_baselines3/td3/__init__.py:F401 + ./stable_baselines3/common/vec_env/__init__.py:F401 +exclude = + # No need to traverse our git directory + .git, + # There's no value in checking cache directories + __pycache__, + # Don't check the doc + docs/ + # This contains our built documentation + build, + # This contains builds of flake8 that we don't want to check + dist + *.egg-info +max-complexity = 15 +# The GitHub editor is 127 chars wide +max-line-length = 127 diff --git a/setup.py b/setup.py index dd1506a..bf9d823 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,4 @@ import os -import sys -import subprocess from setuptools import setup, find_packages with open(os.path.join('stable_baselines3', 'version.txt'), 'r') as file_handler: @@ -63,14 +61,14 @@ from stable_baselines3 import PPO model = PPO('MlpPolicy', 'CartPole-v1').learn(10000) ``` -""" +""" # noqa:E501 setup(name='stable_baselines3', packages=[package for package in find_packages() if package.startswith('stable_baselines3')], package_data={ - 'stable_baselines3': ['py.typed', 'version.txt'] + 'stable_baselines3': ['py.typed', 'version.txt'] }, install_requires=[ 'gym>=0.17', @@ -84,28 +82,32 @@ setup(name='stable_baselines3', 'matplotlib' ], extras_require={ - 'tests': [ - 'pytest', - 'pytest-cov', - 'pytest-env', - 'pytest-xdist', - 'pytype', - ], - 'docs': [ - 'sphinx', - 'sphinx-autobuild', - 'sphinx-rtd-theme', - # For spelling - 'sphinxcontrib.spelling', - # Type hints support - # 'sphinx-autodoc-typehints' - ], - 'extra': [ - # For render - 'opencv-python', - # For atari games, - 'atari_py~=0.2.0', 'pillow' - ] + 'tests': [ + # Run tests and coverage + 'pytest', + 'pytest-cov', + 'pytest-env', + 'pytest-xdist', + # Type check + 'pytype', + # Lint code + 'flake8>=3.8' + ], + 'docs': [ + 'sphinx', + 'sphinx-autobuild', + 'sphinx-rtd-theme', + # For spelling + 'sphinxcontrib.spelling', + # Type hints support + # 'sphinx-autodoc-typehints' + ], + 'extra': [ + # For render + 'opencv-python', + # For atari games, + 'atari_py~=0.2.0', 'pillow' + ] }, description='Pytorch version of Stable Baselines, implementations of reinforcement learning algorithms.', author='Antonin Raffin', diff --git a/stable_baselines3/common/cmd_util.py b/stable_baselines3/common/cmd_util.py index 954bae7..5155104 100644 --- a/stable_baselines3/common/cmd_util.py +++ b/stable_baselines3/common/cmd_util.py @@ -4,9 +4,7 @@ from typing import Dict, Any, Optional, Callable, Type, Union import gym -from stable_baselines3.common import logger from stable_baselines3.common.monitor import Monitor -from stable_baselines3.common.utils import set_random_seed from stable_baselines3.common.atari_wrappers import AtariWrapper from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv diff --git a/stable_baselines3/common/identity_env.py b/stable_baselines3/common/identity_env.py index 2f3ce28..df77f6a 100644 --- a/stable_baselines3/common/identity_env.py +++ b/stable_baselines3/common/identity_env.py @@ -1,4 +1,4 @@ -from typing import List, Union, Optional +from typing import Union, Optional import numpy as np from gym import Env, Space diff --git a/stable_baselines3/common/noise.py b/stable_baselines3/common/noise.py index a8ff63c..f27b542 100644 --- a/stable_baselines3/common/noise.py +++ b/stable_baselines3/common/noise.py @@ -71,8 +71,8 @@ class OrnsteinUhlenbeckActionNoise(ActionNoise): super(OrnsteinUhlenbeckActionNoise, self).__init__() def __call__(self) -> np.ndarray: - noise = self.noise_prev + self._theta * (self._mu - self.noise_prev) * self._dt + \ - self._sigma * np.sqrt(self._dt) * np.random.normal(size=self._mu.shape) + noise = (self.noise_prev + self._theta * (self._mu - self.noise_prev) * self._dt + + self._sigma * np.sqrt(self._dt) * np.random.normal(size=self._mu.shape)) self.noise_prev = noise return noise diff --git a/stable_baselines3/common/vec_env/vec_transpose.py b/stable_baselines3/common/vec_env/vec_transpose.py index e4901b4..3401424 100644 --- a/stable_baselines3/common/vec_env/vec_transpose.py +++ b/stable_baselines3/common/vec_env/vec_transpose.py @@ -6,7 +6,7 @@ from stable_baselines3.common.vec_env.base_vec_env import VecEnv, VecEnvWrapper from stable_baselines3.common.preprocessing import is_image_space if typing.TYPE_CHECKING: - from stable_baselines3.common.type_aliases import GymStepReturn + from stable_baselines3.common.type_aliases import GymStepReturn # noqa: F401 class VecTransposeImage(VecEnvWrapper): diff --git a/tests/test_distributions.py b/tests/test_distributions.py index e9041f8..d1a4bff 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -42,6 +42,7 @@ def test_squashed_gaussian(model_class): actions = dist.get_actions() assert th.max(th.abs(actions)) <= 1.0 + def test_sde_distribution(): n_actions = 1 deterministic_actions = th.ones(N_SAMPLES, n_actions) * 0.1 @@ -95,4 +96,4 @@ def test_categorical(): actions = dist.get_actions() entropy = dist.entropy() log_prob = dist.log_prob(actions) - assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=1e-4) + assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=2e-4) diff --git a/tests/test_envs.py b/tests/test_envs.py index 8d57c2f..5654d06 100644 --- a/tests/test_envs.py +++ b/tests/test_envs.py @@ -46,6 +46,7 @@ def test_high_dimension_action_space(): env = FakeImageEnv() # Patch the action space env.action_space = spaces.Box(low=-1, high=1, shape=(20,), dtype=np.float32) + # Patch to avoid error def patched_step(_action): return env.observation_space.sample(), 0.0, False, {} diff --git a/tests/test_save_load.py b/tests/test_save_load.py index b7c0924..dc2d373 100644 --- a/tests/test_save_load.py +++ b/tests/test_save_load.py @@ -230,7 +230,6 @@ def test_save_load_policy(model_class, policy_str): del policy, actor - policy = policy_class.load("./logs/policy.pkl") if actor_class is not None: actor = actor_class.load("./logs/actor.pkl") diff --git a/tests/test_utils.py b/tests/test_utils.py index 5c029ec..e2317a0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -37,7 +37,7 @@ def test_make_vec_env(env_id, n_envs, vec_env_cls, wrapper_class): @pytest.mark.parametrize("env_id", ['BreakoutNoFrameskip-v4']) @pytest.mark.parametrize("n_envs", [1, 2]) @pytest.mark.parametrize("wrapper_kwargs", [None, dict(clip_reward=False, screen_size=60)]) -def test_make_vec_env(env_id, n_envs, wrapper_kwargs): +def test_make_atari_env(env_id, n_envs, wrapper_kwargs): env_id = 'BreakoutNoFrameskip-v4' env = make_atari_env(env_id, n_envs, wrapper_kwargs=wrapper_kwargs, monitor_dir=None, seed=0) @@ -55,15 +55,14 @@ def test_make_vec_env(env_id, n_envs, wrapper_kwargs): if wrapper_kwargs is not None: assert obs.shape == (n_envs, 60, 60, 1) assert wrapped_atari_env.observation_space.shape == (60, 60, 1) - assert wrapped_atari_env.clip_reward == False + assert wrapped_atari_env.clip_reward is False else: assert obs.shape == (n_envs, 84, 84, 1) assert wrapped_atari_env.observation_space.shape == (84, 84, 1) - assert wrapped_atari_env.clip_reward == True + assert wrapped_atari_env.clip_reward is True assert np.max(np.abs(reward)) < 1.0 - def test_custom_vec_env(tmp_path): """ Stand alone test for a special case (passing a custom VecEnv class) to avoid doubling the number of tests. @@ -73,7 +72,6 @@ def test_custom_vec_env(tmp_path): monitor_dir=monitor_dir, seed=0, vec_env_cls=SubprocVecEnv, vec_env_kwargs={'start_method': None}) - assert env.num_envs == 1 assert isinstance(env, SubprocVecEnv) assert os.path.isdir(monitor_dir) diff --git a/tests/test_vec_check_nan.py b/tests/test_vec_check_nan.py index a04410a..b6bfa18 100644 --- a/tests/test_vec_check_nan.py +++ b/tests/test_vec_check_nan.py @@ -68,5 +68,4 @@ def test_check_nan(): else: assert False - env.step(np.array([[0, 1], [0, 1]])) diff --git a/tests/test_vec_normalize.py b/tests/test_vec_normalize.py index 5ec80c7..294d15b 100644 --- a/tests/test_vec_normalize.py +++ b/tests/test_vec_normalize.py @@ -3,7 +3,8 @@ import pytest import numpy as np from stable_baselines3.common.running_mean_std import RunningMeanStd -from stable_baselines3.common.vec_env import DummyVecEnv, VecNormalize, VecFrameStack, sync_envs_normalization, unwrap_vec_normalize +from stable_baselines3.common.vec_env import (DummyVecEnv, VecNormalize, VecFrameStack, sync_envs_normalization, + unwrap_vec_normalize) from stable_baselines3 import SAC, TD3 ENV_ID = 'Pendulum-v0' @@ -53,8 +54,8 @@ def _make_warmstart_cartpole(): def test_runningmeanstd(): """Test RunningMeanStd object""" for (x_1, x_2, x_3) in [ - (np.random.randn(3), np.random.randn(4), np.random.randn(5)), - (np.random.randn(3, 2), np.random.randn(4, 2), np.random.randn(5, 2))]: + (np.random.randn(3), np.random.randn(4), np.random.randn(5)), + (np.random.randn(3, 2), np.random.randn(4, 2), np.random.randn(5, 2))]: rms = RunningMeanStd(epsilon=0.0, shape=x_1.shape[1:]) x_cat = np.concatenate([x_1, x_2, x_3], axis=0)