Add flake8 linter and Github CI (#19)

* Cleanup code

* Add flake8 lint and github workflow

* Update build matrix

* Relax precision for python3.7
This commit is contained in:
Antonin RAFFIN 2020-05-12 17:55:01 +02:00 committed by GitHub
parent 299c28140e
commit 54f6f5b6fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 130 additions and 45 deletions

View file

@ -24,6 +24,7 @@
- [ ] My change requires a change to the documentation. - [ ] 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 tests accordingly (*required for a bug fix or a new feature*).
- [ ] I have updated the documentation accordingly. - [ ] 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.
<!--- This Template is an edited version of the one from https://github.com/evilsocket/pwnagotchi/ --> <!--- This Template is an edited version of the one from https://github.com/evilsocket/pwnagotchi/ -->

43
.github/workflows/ci.yml vendored Normal file
View file

@ -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

View file

@ -99,6 +99,12 @@ Type checking with `pytype`:
make type make type
``` ```
Codestyle check with `flake8`:
```
make lint
```
Build the documentation: Build the documentation:
``` ```

View file

@ -6,6 +6,13 @@ pytest:
type: type:
pytype 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: doc:
cd docs && make html cd docs && make html
@ -15,7 +22,7 @@ spelling:
clean: clean:
cd docs && make clean cd docs && make clean
.PHONY: clean spelling doc .PHONY: clean spelling doc lint
# Build docker images # Build docker images
# If you do export RELEASE=True, it will also push them # If you do export RELEASE=True, it will also push them

View file

@ -34,6 +34,8 @@ Others:
- Sync ``VecEnvs`` with Stable-Baselines - Sync ``VecEnvs`` with Stable-Baselines
- Update requirement: ``gym>=0.17`` - Update requirement: ``gym>=0.17``
- Added ``.readthedoc.yml`` file - Added ``.readthedoc.yml`` file
- Added ``flake8`` and ``make lint`` command
- Added Github workflow
Documentation: Documentation:
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^

View file

@ -19,3 +19,30 @@ filterwarnings =
[pytype] [pytype]
inputs = stable_baselines3 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

View file

@ -1,6 +1,4 @@
import os import os
import sys
import subprocess
from setuptools import setup, find_packages from setuptools import setup, find_packages
with open(os.path.join('stable_baselines3', 'version.txt'), 'r') as file_handler: 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) model = PPO('MlpPolicy', 'CartPole-v1').learn(10000)
``` ```
""" """ # noqa:E501
setup(name='stable_baselines3', setup(name='stable_baselines3',
packages=[package for package in find_packages() packages=[package for package in find_packages()
if package.startswith('stable_baselines3')], if package.startswith('stable_baselines3')],
package_data={ package_data={
'stable_baselines3': ['py.typed', 'version.txt'] 'stable_baselines3': ['py.typed', 'version.txt']
}, },
install_requires=[ install_requires=[
'gym>=0.17', 'gym>=0.17',
@ -84,28 +82,32 @@ setup(name='stable_baselines3',
'matplotlib' 'matplotlib'
], ],
extras_require={ extras_require={
'tests': [ 'tests': [
'pytest', # Run tests and coverage
'pytest-cov', 'pytest',
'pytest-env', 'pytest-cov',
'pytest-xdist', 'pytest-env',
'pytype', 'pytest-xdist',
], # Type check
'docs': [ 'pytype',
'sphinx', # Lint code
'sphinx-autobuild', 'flake8>=3.8'
'sphinx-rtd-theme', ],
# For spelling 'docs': [
'sphinxcontrib.spelling', 'sphinx',
# Type hints support 'sphinx-autobuild',
# 'sphinx-autodoc-typehints' 'sphinx-rtd-theme',
], # For spelling
'extra': [ 'sphinxcontrib.spelling',
# For render # Type hints support
'opencv-python', # 'sphinx-autodoc-typehints'
# For atari games, ],
'atari_py~=0.2.0', 'pillow' '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.', description='Pytorch version of Stable Baselines, implementations of reinforcement learning algorithms.',
author='Antonin Raffin', author='Antonin Raffin',

View file

@ -4,9 +4,7 @@ from typing import Dict, Any, Optional, Callable, Type, Union
import gym import gym
from stable_baselines3.common import logger
from stable_baselines3.common.monitor import Monitor 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.atari_wrappers import AtariWrapper
from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv

View file

@ -1,4 +1,4 @@
from typing import List, Union, Optional from typing import Union, Optional
import numpy as np import numpy as np
from gym import Env, Space from gym import Env, Space

View file

@ -71,8 +71,8 @@ class OrnsteinUhlenbeckActionNoise(ActionNoise):
super(OrnsteinUhlenbeckActionNoise, self).__init__() super(OrnsteinUhlenbeckActionNoise, self).__init__()
def __call__(self) -> np.ndarray: def __call__(self) -> np.ndarray:
noise = self.noise_prev + self._theta * (self._mu - self.noise_prev) * self._dt + \ 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._sigma * np.sqrt(self._dt) * np.random.normal(size=self._mu.shape))
self.noise_prev = noise self.noise_prev = noise
return noise return noise

View file

@ -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 from stable_baselines3.common.preprocessing import is_image_space
if typing.TYPE_CHECKING: 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): class VecTransposeImage(VecEnvWrapper):

View file

@ -42,6 +42,7 @@ def test_squashed_gaussian(model_class):
actions = dist.get_actions() actions = dist.get_actions()
assert th.max(th.abs(actions)) <= 1.0 assert th.max(th.abs(actions)) <= 1.0
def test_sde_distribution(): def test_sde_distribution():
n_actions = 1 n_actions = 1
deterministic_actions = th.ones(N_SAMPLES, n_actions) * 0.1 deterministic_actions = th.ones(N_SAMPLES, n_actions) * 0.1
@ -95,4 +96,4 @@ def test_categorical():
actions = dist.get_actions() actions = dist.get_actions()
entropy = dist.entropy() entropy = dist.entropy()
log_prob = dist.log_prob(actions) 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)

View file

@ -46,6 +46,7 @@ def test_high_dimension_action_space():
env = FakeImageEnv() env = FakeImageEnv()
# Patch the action space # Patch the action space
env.action_space = spaces.Box(low=-1, high=1, shape=(20,), dtype=np.float32) env.action_space = spaces.Box(low=-1, high=1, shape=(20,), dtype=np.float32)
# Patch to avoid error # Patch to avoid error
def patched_step(_action): def patched_step(_action):
return env.observation_space.sample(), 0.0, False, {} return env.observation_space.sample(), 0.0, False, {}

View file

@ -230,7 +230,6 @@ def test_save_load_policy(model_class, policy_str):
del policy, actor del policy, actor
policy = policy_class.load("./logs/policy.pkl") policy = policy_class.load("./logs/policy.pkl")
if actor_class is not None: if actor_class is not None:
actor = actor_class.load("./logs/actor.pkl") actor = actor_class.load("./logs/actor.pkl")

View file

@ -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("env_id", ['BreakoutNoFrameskip-v4'])
@pytest.mark.parametrize("n_envs", [1, 2]) @pytest.mark.parametrize("n_envs", [1, 2])
@pytest.mark.parametrize("wrapper_kwargs", [None, dict(clip_reward=False, screen_size=60)]) @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_id = 'BreakoutNoFrameskip-v4'
env = make_atari_env(env_id, n_envs, env = make_atari_env(env_id, n_envs,
wrapper_kwargs=wrapper_kwargs, monitor_dir=None, seed=0) 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: if wrapper_kwargs is not None:
assert obs.shape == (n_envs, 60, 60, 1) assert obs.shape == (n_envs, 60, 60, 1)
assert wrapped_atari_env.observation_space.shape == (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: else:
assert obs.shape == (n_envs, 84, 84, 1) assert obs.shape == (n_envs, 84, 84, 1)
assert wrapped_atari_env.observation_space.shape == (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 assert np.max(np.abs(reward)) < 1.0
def test_custom_vec_env(tmp_path): 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. 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, monitor_dir=monitor_dir, seed=0,
vec_env_cls=SubprocVecEnv, vec_env_kwargs={'start_method': None}) vec_env_cls=SubprocVecEnv, vec_env_kwargs={'start_method': None})
assert env.num_envs == 1 assert env.num_envs == 1
assert isinstance(env, SubprocVecEnv) assert isinstance(env, SubprocVecEnv)
assert os.path.isdir(monitor_dir) assert os.path.isdir(monitor_dir)

View file

@ -68,5 +68,4 @@ def test_check_nan():
else: else:
assert False assert False
env.step(np.array([[0, 1], [0, 1]])) env.step(np.array([[0, 1], [0, 1]]))

View file

@ -3,7 +3,8 @@ import pytest
import numpy as np import numpy as np
from stable_baselines3.common.running_mean_std import RunningMeanStd 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 from stable_baselines3 import SAC, TD3
ENV_ID = 'Pendulum-v0' ENV_ID = 'Pendulum-v0'
@ -53,8 +54,8 @@ def _make_warmstart_cartpole():
def test_runningmeanstd(): def test_runningmeanstd():
"""Test RunningMeanStd object""" """Test RunningMeanStd object"""
for (x_1, x_2, x_3) in [ for (x_1, x_2, x_3) in [
(np.random.randn(3), np.random.randn(4), np.random.randn(5)), (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, 2), np.random.randn(4, 2), np.random.randn(5, 2))]:
rms = RunningMeanStd(epsilon=0.0, shape=x_1.shape[1:]) rms = RunningMeanStd(epsilon=0.0, shape=x_1.shape[1:])
x_cat = np.concatenate([x_1, x_2, x_3], axis=0) x_cat = np.concatenate([x_1, x_2, x_3], axis=0)