From 9d787aa9cc3d0c961fa1296bbc0aa611ac01d9f7 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 8 Nov 2018 22:53:31 -0500 Subject: [PATCH 01/65] Remove unused validations from v1 config --- readthedocs/config/config.py | 73 -------------------- readthedocs/config/tests/test_config.py | 91 +------------------------ 2 files changed, 1 insertion(+), 163 deletions(-) diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 4b7c48f56..c91a45462 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -6,7 +6,6 @@ from __future__ import division, print_function, unicode_literals import os -import re from contextlib import contextmanager import six @@ -22,7 +21,6 @@ from .validation import ( validate_bool, validate_choice, validate_dict, - validate_directory, validate_file, validate_list, validate_string, @@ -43,12 +41,8 @@ CONFIG_FILENAME_REGEX = r'^\.?readthedocs.ya?ml$' CONFIG_NOT_SUPPORTED = 'config-not-supported' VERSION_INVALID = 'version-invalid' -BASE_INVALID = 'base-invalid' -BASE_NOT_A_DIR = 'base-not-a-directory' CONFIG_SYNTAX_INVALID = 'config-syntax-invalid' CONFIG_REQUIRED = 'config-required' -NAME_REQUIRED = 'name-required' -NAME_INVALID = 'name-invalid' CONF_FILE_REQUIRED = 'conf-file-required' PYTHON_INVALID = 'python-invalid' SUBMODULES_INVALID = 'submodules-invalid' @@ -263,12 +257,6 @@ class BuildConfigV1(BuildConfigBase): """Version 1 of the configuration file.""" - BASE_INVALID_MESSAGE = 'Invalid value for base: {base}' - BASE_NOT_A_DIR_MESSAGE = '"base" is not a directory: {base}' - NAME_REQUIRED_MESSAGE = 'Missing key "name"' - NAME_INVALID_MESSAGE = ( - 'Invalid name "{name}". Valid values must match {name_re}' - ) CONF_FILE_REQUIRED_MESSAGE = 'Missing key "conf_file"' PYTHON_INVALID_MESSAGE = '"python" section must be a mapping.' PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE = ( @@ -306,63 +294,17 @@ class BuildConfigV1(BuildConfigBase): ``readthedocs.yml`` config file if not set """ # Validate env_config. - # TODO: this isn't used - self._config['output_base'] = self.validate_output_base() - # Validate the build environment first # Must happen before `validate_python`! self._config['build'] = self.validate_build() # Validate raw_config. Order matters. - # TODO: this isn't used - self._config['name'] = self.validate_name() - # TODO: this isn't used - self._config['base'] = self.validate_base() self._config['python'] = self.validate_python() self._config['formats'] = self.validate_formats() self._config['conda'] = self.validate_conda() self._config['requirements_file'] = self.validate_requirements_file() - def validate_output_base(self): - """Validates that ``output_base`` exists and set its absolute path.""" - assert 'output_base' in self.env_config, ( - '"output_base" required in "env_config"') - output_base = os.path.abspath( - os.path.join( - self.env_config.get('output_base', self.base_path), - ) - ) - return output_base - - def validate_name(self): - """Validates that name exists.""" - name = self.raw_config.get('name', None) - if not name: - name = self.env_config.get('name', None) - if not name: - self.error('name', self.NAME_REQUIRED_MESSAGE, code=NAME_REQUIRED) - name_re = r'^[-_.0-9a-zA-Z]+$' - if not re.match(name_re, name): - self.error( - 'name', - self.NAME_INVALID_MESSAGE.format( - name=name, - name_re=name_re), - code=NAME_INVALID) - - return name - - def validate_base(self): - """Validates that path is a valid directory.""" - if 'base' in self.raw_config: - base = self.raw_config['base'] - else: - base = self.base_path - with self.catch_validation_error('base'): - base = validate_directory(base, self.base_path) - return base - def validate_build(self): """ Validate the build config settings. @@ -542,21 +484,6 @@ class BuildConfigV1(BuildConfigBase): return formats - @property - def name(self): - """The project name.""" - return self._config['name'] - - @property - def base(self): - """The base directory.""" - return self._config['base'] - - @property - def output_base(self): - """The output base.""" - return self._config['output_base'] - @property def formats(self): """The documentation formats to be built.""" diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index f623881a6..667e1d220 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -24,8 +24,6 @@ from readthedocs.config.config import ( CONFIG_NOT_SUPPORTED, CONFIG_REQUIRED, INVALID_KEY, - NAME_INVALID, - NAME_REQUIRED, PYTHON_INVALID, VERSION_INVALID, ) @@ -34,8 +32,6 @@ from readthedocs.config.validation import ( INVALID_BOOL, INVALID_CHOICE, INVALID_LIST, - INVALID_PATH, - INVALID_STRING, VALUE_NOT_FOUND, ValidationError, ) @@ -90,10 +86,7 @@ def get_build_config(config, env_config=None, source_file='readthedocs.yml'): def get_env_config(extra=None): """Get the minimal env_config for the configuration object.""" - defaults = { - 'output_base': '', - 'name': 'name', - } + defaults = {} if extra is None: extra = {} defaults.update(extra) @@ -186,30 +179,6 @@ def test_build_config_has_list_with_single_empty_value(tmpdir): assert build.formats == [] -def test_config_requires_name(): - build = BuildConfigV1( - {'output_base': ''}, - {}, - source_file='readthedocs.yml', - ) - with raises(InvalidConfig) as excinfo: - build.validate() - assert excinfo.value.key == 'name' - assert excinfo.value.code == NAME_REQUIRED - - -def test_build_requires_valid_name(): - build = BuildConfigV1( - {'output_base': ''}, - {'name': 'with/slashes'}, - source_file='readthedocs.yml', - ) - with raises(InvalidConfig) as excinfo: - build.validate() - assert excinfo.value.key == 'name' - assert excinfo.value.code == NAME_INVALID - - def test_version(): build = get_build_config({}, get_env_config()) assert build.version == '1' @@ -528,62 +497,10 @@ def test_valid_build_config(): source_file='readthedocs.yml', ) build.validate() - assert build.name == 'docs' - assert build.base assert build.python assert build.python.install_with_setup is False assert build.python.install_with_pip is False assert build.python.use_system_site_packages is False - assert build.output_base - - -class TestValidateBase(object): - - def test_it_validates_to_abspath(self, tmpdir): - apply_fs(tmpdir, {'configs': minimal_config, 'docs': {}}) - with tmpdir.as_cwd(): - source_file = str(tmpdir.join('configs', 'readthedocs.yml')) - build = BuildConfigV1( - get_env_config(), - {'base': '../docs'}, - source_file=source_file, - ) - build.validate() - assert build.base == str(tmpdir.join('docs')) - - @patch('readthedocs.config.config.validate_directory') - def test_it_uses_validate_directory(self, validate_directory): - validate_directory.return_value = 'path' - build = get_build_config({'base': '../my-path'}, get_env_config()) - build.validate() - # Test for first argument to validate_directory - args, kwargs = validate_directory.call_args - assert args[0] == '../my-path' - - def test_it_fails_if_base_is_not_a_string(self, tmpdir): - apply_fs(tmpdir, minimal_config) - with tmpdir.as_cwd(): - build = BuildConfigV1( - get_env_config(), - {'base': 1}, - source_file=str(tmpdir.join('readthedocs.yml')), - ) - with raises(InvalidConfig) as excinfo: - build.validate() - assert excinfo.value.key == 'base' - assert excinfo.value.code == INVALID_STRING - - def test_it_fails_if_base_does_not_exist(self, tmpdir): - apply_fs(tmpdir, minimal_config) - build = BuildConfigV1( - get_env_config(), - {'base': 'docs'}, - source_file=str(tmpdir.join('readthedocs.yml')), - ) - with raises(InvalidConfig) as excinfo: - build.validate() - assert excinfo.value.key == 'base' - assert excinfo.value.code == INVALID_PATH class TestValidateBuild(object): @@ -752,16 +669,10 @@ def test_build_validate_calls_all_subvalidators(tmpdir): ) with patch.multiple( BuildConfigV1, - validate_base=DEFAULT, - validate_name=DEFAULT, validate_python=DEFAULT, - validate_output_base=DEFAULT, ): build.validate() - BuildConfigV1.validate_base.assert_called_with() - BuildConfigV1.validate_name.assert_called_with() BuildConfigV1.validate_python.assert_called_with() - BuildConfigV1.validate_output_base.assert_called_with() def test_load_calls_validate(tmpdir): From 51e693eae05c37d68a71008188e347049705b5cc Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Fri, 9 Nov 2018 11:34:23 -0500 Subject: [PATCH 02/65] Clean up tests --- readthedocs/config/tests/test_config.py | 236 +++++++++++------------- 1 file changed, 103 insertions(+), 133 deletions(-) diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 667e1d220..e015c22ba 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -38,41 +38,13 @@ from readthedocs.config.validation import ( from .utils import apply_fs -env_config = { - 'output_base': '/tmp', -} - -minimal_config = { - 'name': 'docs', -} - -config_with_explicit_empty_list = { - 'readthedocs.yml': ''' -name: docs -formats: [] -''', -} - -minimal_config_dir = { - 'readthedocs.yml': '''\ -name: docs -''', -} - -multiple_config_dir = { - 'readthedocs.yml': ''' -name: first ---- -name: second - ''', - 'nested': minimal_config_dir, -} - -yaml_extension_config_dir = { - 'readthedocs.yaml': '''\ -name: docs -type: sphinx -''' +yaml_config_dir = { + 'readthedocs.yml': textwrap.dedent( + ''' + formats: + - pdf + ''' + ), } @@ -84,15 +56,6 @@ def get_build_config(config, env_config=None, source_file='readthedocs.yml'): ) -def get_env_config(extra=None): - """Get the minimal env_config for the configuration object.""" - defaults = {} - if extra is None: - extra = {} - defaults.update(extra) - return defaults - - @pytest.mark.parametrize('files', [ {'readthedocs.ymlmore': ''}, {'first': {'readthedocs.yml': ''}}, {'startreadthedocs.yml': ''}, {'second': {'confuser.txt': 'content'}}, @@ -104,7 +67,7 @@ def test_load_no_config_file(tmpdir, files): apply_fs(tmpdir, files) base = str(tmpdir) with raises(ConfigError) as e: - load(base, env_config) + load(base, {}) assert e.value.code == CONFIG_REQUIRED @@ -114,13 +77,13 @@ def test_load_empty_config_file(tmpdir): }) base = str(tmpdir) with raises(ConfigError): - load(base, env_config) + load(base, {}) def test_minimal_config(tmpdir): - apply_fs(tmpdir, minimal_config_dir) + apply_fs(tmpdir, yaml_config_dir) base = str(tmpdir) - build = load(base, env_config) + build = load(base, {'formats': []}) assert isinstance(build, BuildConfigV1) @@ -131,7 +94,7 @@ def test_load_version1(tmpdir): ''') }) base = str(tmpdir) - build = load(base, get_env_config({'allow_v2': True})) + build = load(base, {'allow_v2': True}) assert isinstance(build, BuildConfigV1) @@ -142,7 +105,7 @@ def test_load_version2(tmpdir): ''') }) base = str(tmpdir) - build = load(base, get_env_config({'allow_v2': True})) + build = load(base, {'allow_v2': True}) assert isinstance(build, BuildConfigV2) @@ -154,59 +117,70 @@ def test_load_unknow_version(tmpdir): }) base = str(tmpdir) with raises(ConfigError) as excinfo: - load(base, get_env_config({'allow_v2': True})) + load(base, {'allow_v2': True}) assert excinfo.value.code == VERSION_INVALID def test_yaml_extension(tmpdir): """Make sure it's capable of loading the 'readthedocs' file with a 'yaml' extension.""" - apply_fs(tmpdir, yaml_extension_config_dir) + apply_fs(tmpdir, { + 'readthedocs.yaml': textwrap.dedent( + ''' + python: + version: 3 + ''' + ), + }) base = str(tmpdir) - config = load(base, env_config) + config = load(base, {}) assert isinstance(config, BuildConfigV1) def test_build_config_has_source_file(tmpdir): - base = str(apply_fs(tmpdir, minimal_config_dir)) - build = load(base, env_config) + base = str(apply_fs(tmpdir, yaml_config_dir)) + build = load(base, {}) assert build.source_file == os.path.join(base, 'readthedocs.yml') def test_build_config_has_list_with_single_empty_value(tmpdir): - base = str(apply_fs(tmpdir, config_with_explicit_empty_list)) - build = load(base, env_config) + base = str(apply_fs(tmpdir, { + 'readthedocs.yml': textwrap.dedent( + ''' + formats: [] + ''' + ) + })) + build = load(base, {}) assert isinstance(build, BuildConfigV1) assert build.formats == [] def test_version(): - build = get_build_config({}, get_env_config()) + build = get_build_config({}, {}) assert build.version == '1' def test_doc_type(): build = get_build_config( {}, - get_env_config( - { - 'defaults': { - 'doctype': 'sphinx', - }, - } - ) + { + 'defaults': { + 'doctype': 'sphinx', + }, + } ) build.validate() assert build.doctype == 'sphinx' def test_empty_python_section_is_valid(): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() assert build.python def test_python_section_must_be_dict(): - build = get_build_config({'python': 123}, get_env_config()) + build = get_build_config({'python': 123}, {}) with raises(InvalidConfig) as excinfo: build.validate() assert excinfo.value.key == 'python' @@ -214,7 +188,7 @@ def test_python_section_must_be_dict(): def test_use_system_site_packages_defaults_to_false(): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() # Default is False. assert not build.python.use_system_site_packages @@ -225,13 +199,13 @@ def test_use_system_site_packages_repects_default_value(value): defaults = { 'use_system_packages': value, } - build = get_build_config({}, get_env_config({'defaults': defaults})) + build = get_build_config({}, {'defaults': defaults}) build.validate() assert build.python.use_system_site_packages is value def test_python_pip_install_default(): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() # Default is False. assert build.python.install_with_pip is False @@ -240,7 +214,7 @@ def test_python_pip_install_default(): class TestValidatePythonExtraRequirements(object): def test_it_defaults_to_list(self): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() # Default is an empty list. assert build.python.extra_requirements == [] @@ -248,7 +222,7 @@ class TestValidatePythonExtraRequirements(object): def test_it_validates_is_a_list(self): build = get_build_config( {'python': {'extra_requirements': 'invalid'}}, - get_env_config(), + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -265,7 +239,7 @@ class TestValidatePythonExtraRequirements(object): 'extra_requirements': ['tests'], }, }, - get_env_config(), + {}, ) build.validate() validate_string.assert_any_call('tests') @@ -274,14 +248,14 @@ class TestValidatePythonExtraRequirements(object): class TestValidateUseSystemSitePackages(object): def test_it_defaults_to_false(self): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() assert build.python.use_system_site_packages is False def test_it_validates_value(self): build = get_build_config( {'python': {'use_system_site_packages': 'invalid'}}, - get_env_config(), + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -293,7 +267,7 @@ class TestValidateUseSystemSitePackages(object): validate_bool.return_value = True build = get_build_config( {'python': {'use_system_site_packages': 'to-validate'}}, - get_env_config(), + {}, ) build.validate() validate_bool.assert_any_call('to-validate') @@ -302,14 +276,14 @@ class TestValidateUseSystemSitePackages(object): class TestValidateSetupPyInstall(object): def test_it_defaults_to_false(self): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() assert build.python.install_with_setup is False def test_it_validates_value(self): build = get_build_config( {'python': {'setup_py_install': 'this-is-string'}}, - get_env_config(), + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -321,7 +295,7 @@ class TestValidateSetupPyInstall(object): validate_bool.return_value = True build = get_build_config( {'python': {'setup_py_install': 'to-validate'}}, - get_env_config(), + {}, ) build.validate() validate_bool.assert_any_call('to-validate') @@ -330,7 +304,7 @@ class TestValidateSetupPyInstall(object): class TestValidatePythonVersion(object): def test_it_defaults_to_a_valid_version(self): - build = get_build_config({'python': {}}, get_env_config()) + build = get_build_config({'python': {}}, {}) build.validate() assert build.python.version == 2 assert build.python_interpreter == 'python2.7' @@ -339,7 +313,7 @@ class TestValidatePythonVersion(object): def test_it_supports_other_versions(self): build = get_build_config( {'python': {'version': 3.5}}, - get_env_config(), + {}, ) build.validate() assert build.python.version == 3.5 @@ -349,7 +323,7 @@ class TestValidatePythonVersion(object): def test_it_validates_versions_out_of_range(self): build = get_build_config( {'python': {'version': 1.0}}, - get_env_config(), + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -359,7 +333,7 @@ class TestValidatePythonVersion(object): def test_it_validates_wrong_type(self): build = get_build_config( {'python': {'version': 'this-is-string'}}, - get_env_config(), + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -369,7 +343,7 @@ class TestValidatePythonVersion(object): def test_it_validates_wrong_type_right_value(self): build = get_build_config( {'python': {'version': '3.5'}}, - get_env_config(), + {}, ) build.validate() assert build.python.version == 3.5 @@ -378,7 +352,7 @@ class TestValidatePythonVersion(object): build = get_build_config( {'python': {'version': '3'}}, - get_env_config(), + {}, ) build.validate() assert build.python.version == 3 @@ -388,12 +362,10 @@ class TestValidatePythonVersion(object): def test_it_validates_env_supported_versions(self): build = get_build_config( {'python': {'version': 3.6}}, - env_config=get_env_config( - { - 'python': {'supported_versions': [3.5]}, - 'build': {'image': 'custom'}, - } - ) + env_config={ + 'python': {'supported_versions': [3.5]}, + 'build': {'image': 'custom'}, + }, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -402,12 +374,10 @@ class TestValidatePythonVersion(object): build = get_build_config( {'python': {'version': 3.6}}, - env_config=get_env_config( - { - 'python': {'supported_versions': [3.5, 3.6]}, - 'build': {'image': 'custom'}, - } - ) + env_config={ + 'python': {'supported_versions': [3.5, 3.6]}, + 'build': {'image': 'custom'}, + }, ) build.validate() assert build.python.version == 3.6 @@ -421,7 +391,7 @@ class TestValidatePythonVersion(object): } build = get_build_config( {}, - get_env_config({'defaults': defaults}), + {'defaults': defaults}, ) build.validate() assert build.python.version == value @@ -430,34 +400,34 @@ class TestValidatePythonVersion(object): class TestValidateFormats(object): def test_it_defaults_to_empty(self): - build = get_build_config({}, get_env_config()) + build = get_build_config({}, {}) build.validate() assert build.formats == [] def test_it_gets_set_correctly(self): - build = get_build_config({'formats': ['pdf']}, get_env_config()) + build = get_build_config({'formats': ['pdf']}, {}) build.validate() assert build.formats == ['pdf'] def test_formats_can_be_null(self): - build = get_build_config({'formats': None}, get_env_config()) + build = get_build_config({'formats': None}, {}) build.validate() assert build.formats == [] def test_formats_with_previous_none(self): - build = get_build_config({'formats': ['none']}, get_env_config()) + build = get_build_config({'formats': ['none']}, {}) build.validate() assert build.formats == [] def test_formats_can_be_empty(self): - build = get_build_config({'formats': []}, get_env_config()) + build = get_build_config({'formats': []}, {}) build.validate() assert build.formats == [] def test_all_valid_formats(self): build = get_build_config( {'formats': ['pdf', 'htmlzip', 'epub']}, - get_env_config() + {}, ) build.validate() assert build.formats == ['pdf', 'htmlzip', 'epub'] @@ -465,7 +435,7 @@ class TestValidateFormats(object): def test_cant_have_none_as_format(self): build = get_build_config( {'formats': ['htmlzip', None]}, - get_env_config() + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -475,7 +445,7 @@ class TestValidateFormats(object): def test_formats_have_only_allowed_values(self): build = get_build_config( {'formats': ['htmlzip', 'csv']}, - get_env_config() + {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -483,7 +453,7 @@ class TestValidateFormats(object): assert excinfo.value.code == INVALID_CHOICE def test_only_list_type(self): - build = get_build_config({'formats': 'no-list'}, get_env_config()) + build = get_build_config({'formats': 'no-list'}, {}) with raises(InvalidConfig) as excinfo: build.validate() assert excinfo.value.key == 'format' @@ -492,8 +462,8 @@ class TestValidateFormats(object): def test_valid_build_config(): build = BuildConfigV1( - env_config, - minimal_config, + {}, + {}, source_file='readthedocs.yml', ) build.validate() @@ -506,9 +476,9 @@ def test_valid_build_config(): class TestValidateBuild(object): def test_it_fails_if_build_is_invalid_option(self, tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) build = BuildConfigV1( - get_env_config(), + {}, {'build': {'image': 3.0}}, source_file=str(tmpdir.join('readthedocs.yml')), ) @@ -518,7 +488,7 @@ class TestValidateBuild(object): assert excinfo.value.code == INVALID_CHOICE def test_it_fails_on_python_validation(self, tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) build = BuildConfigV1( {}, { @@ -534,7 +504,7 @@ class TestValidateBuild(object): assert excinfo.value.code == INVALID_CHOICE def test_it_works_on_python_validation(self, tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) build = BuildConfigV1( {}, { @@ -547,9 +517,9 @@ class TestValidateBuild(object): build.validate_python() def test_it_works(self, tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) build = BuildConfigV1( - get_env_config(), + {}, {'build': {'image': 'latest'}}, source_file=str(tmpdir.join('readthedocs.yml')), ) @@ -557,9 +527,9 @@ class TestValidateBuild(object): assert build.build.image == 'readthedocs/build:latest' def test_default(self, tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) build = BuildConfigV1( - get_env_config(), + {}, {}, source_file=str(tmpdir.join('readthedocs.yml')), ) @@ -569,12 +539,12 @@ class TestValidateBuild(object): @pytest.mark.parametrize( 'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest']) def test_it_priorities_image_from_env_config(self, tmpdir, image): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, yaml_config_dir) defaults = { 'build_image': image, } build = BuildConfigV1( - get_env_config({'defaults': defaults}), + {'defaults': defaults}, {'build': {'image': 'latest'}}, source_file=str(tmpdir.join('readthedocs.yml')), ) @@ -583,7 +553,7 @@ class TestValidateBuild(object): def test_use_conda_default_false(): - build = get_build_config({}, get_env_config()) + build = get_build_config({}, {}) build.validate() assert build.conda is None @@ -591,7 +561,7 @@ def test_use_conda_default_false(): def test_use_conda_respects_config(): build = get_build_config( {'conda': {}}, - get_env_config(), + {}, ) build.validate() assert isinstance(build.conda, Conda) @@ -601,7 +571,7 @@ def test_validates_conda_file(tmpdir): apply_fs(tmpdir, {'environment.yml': ''}) build = get_build_config( {'conda': {'file': 'environment.yml'}}, - get_env_config(), + {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -610,7 +580,7 @@ def test_validates_conda_file(tmpdir): def test_requirements_file_empty(): - build = get_build_config({}, get_env_config()) + build = get_build_config({}, {}) build.validate() assert build.python.requirements is None @@ -622,7 +592,7 @@ def test_requirements_file_repects_default_value(tmpdir): } build = get_build_config( {}, - get_env_config({'defaults': defaults}), + {'defaults': defaults}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -633,7 +603,7 @@ def test_requirements_file_respects_configuration(tmpdir): apply_fs(tmpdir, {'requirements.txt': ''}) build = get_build_config( {'requirements_file': 'requirements.txt'}, - get_env_config(), + {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -643,7 +613,7 @@ def test_requirements_file_respects_configuration(tmpdir): def test_requirements_file_is_null(tmpdir): build = get_build_config( {'requirements_file': None}, - get_env_config(), + {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -653,7 +623,7 @@ def test_requirements_file_is_null(tmpdir): def test_requirements_file_is_blank(tmpdir): build = get_build_config( {'requirements_file': ''}, - get_env_config(), + {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -661,7 +631,7 @@ def test_requirements_file_is_blank(tmpdir): def test_build_validate_calls_all_subvalidators(tmpdir): - apply_fs(tmpdir, minimal_config) + apply_fs(tmpdir, {}) build = BuildConfigV1( {}, {}, @@ -676,15 +646,15 @@ def test_build_validate_calls_all_subvalidators(tmpdir): def test_load_calls_validate(tmpdir): - apply_fs(tmpdir, minimal_config_dir) + apply_fs(tmpdir, yaml_config_dir) base = str(tmpdir) with patch.object(BuildConfigV1, 'validate') as build_validate: - load(base, env_config) + load(base, {}) assert build_validate.call_count == 1 def test_raise_config_not_supported(): - build = get_build_config({}, get_env_config()) + build = get_build_config({}, {}) build.validate() with raises(ConfigOptionNotSupportedError) as excinfo: build.redirects @@ -710,12 +680,12 @@ def test_as_dict(tmpdir): }, 'requirements_file': 'requirements.txt', }, - get_env_config({ + { 'defaults': { 'doctype': 'sphinx', 'sphinx_configuration': None, }, - }), + }, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() From e4d2e5b3bf729001f0c8834c1cecf60976d726d1 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Fri, 9 Nov 2018 11:36:10 -0500 Subject: [PATCH 03/65] Remove unnecessary values --- readthedocs/doc_builder/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py index 00a0095bc..c9ee021a2 100644 --- a/readthedocs/doc_builder/config.py +++ b/readthedocs/doc_builder/config.py @@ -43,8 +43,6 @@ def load_yaml_config(version): 'build': { 'image': img_name, }, - 'output_base': '', - 'name': version.slug, 'defaults': { 'install_project': project.install_project, 'formats': get_default_formats(project), From 421e1f80a7e697ad085908e6a38eede1ecf1260d Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Fri, 9 Nov 2018 11:56:38 -0500 Subject: [PATCH 04/65] More clean up --- readthedocs/config/tests/test_config.py | 57 +++++++------------ .../tests/test_config_integration.py | 2 - 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index e015c22ba..593453d8c 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -83,7 +83,7 @@ def test_load_empty_config_file(tmpdir): def test_minimal_config(tmpdir): apply_fs(tmpdir, yaml_config_dir) base = str(tmpdir) - build = load(base, {'formats': []}) + build = load(base, {}) assert isinstance(build, BuildConfigV1) @@ -156,7 +156,7 @@ def test_build_config_has_list_with_single_empty_value(tmpdir): def test_version(): - build = get_build_config({}, {}) + build = get_build_config({}) assert build.version == '1' @@ -174,13 +174,13 @@ def test_doc_type(): def test_empty_python_section_is_valid(): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() assert build.python def test_python_section_must_be_dict(): - build = get_build_config({'python': 123}, {}) + build = get_build_config({'python': 123}) with raises(InvalidConfig) as excinfo: build.validate() assert excinfo.value.key == 'python' @@ -188,7 +188,7 @@ def test_python_section_must_be_dict(): def test_use_system_site_packages_defaults_to_false(): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() # Default is False. assert not build.python.use_system_site_packages @@ -205,7 +205,7 @@ def test_use_system_site_packages_repects_default_value(value): def test_python_pip_install_default(): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() # Default is False. assert build.python.install_with_pip is False @@ -214,7 +214,7 @@ def test_python_pip_install_default(): class TestValidatePythonExtraRequirements(object): def test_it_defaults_to_list(self): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() # Default is an empty list. assert build.python.extra_requirements == [] @@ -222,7 +222,6 @@ class TestValidatePythonExtraRequirements(object): def test_it_validates_is_a_list(self): build = get_build_config( {'python': {'extra_requirements': 'invalid'}}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -239,7 +238,6 @@ class TestValidatePythonExtraRequirements(object): 'extra_requirements': ['tests'], }, }, - {}, ) build.validate() validate_string.assert_any_call('tests') @@ -248,14 +246,13 @@ class TestValidatePythonExtraRequirements(object): class TestValidateUseSystemSitePackages(object): def test_it_defaults_to_false(self): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() assert build.python.use_system_site_packages is False def test_it_validates_value(self): build = get_build_config( {'python': {'use_system_site_packages': 'invalid'}}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -267,7 +264,6 @@ class TestValidateUseSystemSitePackages(object): validate_bool.return_value = True build = get_build_config( {'python': {'use_system_site_packages': 'to-validate'}}, - {}, ) build.validate() validate_bool.assert_any_call('to-validate') @@ -276,14 +272,13 @@ class TestValidateUseSystemSitePackages(object): class TestValidateSetupPyInstall(object): def test_it_defaults_to_false(self): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() assert build.python.install_with_setup is False def test_it_validates_value(self): build = get_build_config( {'python': {'setup_py_install': 'this-is-string'}}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -295,7 +290,6 @@ class TestValidateSetupPyInstall(object): validate_bool.return_value = True build = get_build_config( {'python': {'setup_py_install': 'to-validate'}}, - {}, ) build.validate() validate_bool.assert_any_call('to-validate') @@ -304,7 +298,7 @@ class TestValidateSetupPyInstall(object): class TestValidatePythonVersion(object): def test_it_defaults_to_a_valid_version(self): - build = get_build_config({'python': {}}, {}) + build = get_build_config({'python': {}}) build.validate() assert build.python.version == 2 assert build.python_interpreter == 'python2.7' @@ -313,7 +307,6 @@ class TestValidatePythonVersion(object): def test_it_supports_other_versions(self): build = get_build_config( {'python': {'version': 3.5}}, - {}, ) build.validate() assert build.python.version == 3.5 @@ -323,7 +316,6 @@ class TestValidatePythonVersion(object): def test_it_validates_versions_out_of_range(self): build = get_build_config( {'python': {'version': 1.0}}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -333,7 +325,6 @@ class TestValidatePythonVersion(object): def test_it_validates_wrong_type(self): build = get_build_config( {'python': {'version': 'this-is-string'}}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -343,7 +334,6 @@ class TestValidatePythonVersion(object): def test_it_validates_wrong_type_right_value(self): build = get_build_config( {'python': {'version': '3.5'}}, - {}, ) build.validate() assert build.python.version == 3.5 @@ -352,7 +342,6 @@ class TestValidatePythonVersion(object): build = get_build_config( {'python': {'version': '3'}}, - {}, ) build.validate() assert build.python.version == 3 @@ -400,34 +389,33 @@ class TestValidatePythonVersion(object): class TestValidateFormats(object): def test_it_defaults_to_empty(self): - build = get_build_config({}, {}) + build = get_build_config({}) build.validate() assert build.formats == [] def test_it_gets_set_correctly(self): - build = get_build_config({'formats': ['pdf']}, {}) + build = get_build_config({'formats': ['pdf']}) build.validate() assert build.formats == ['pdf'] def test_formats_can_be_null(self): - build = get_build_config({'formats': None}, {}) + build = get_build_config({'formats': None}) build.validate() assert build.formats == [] def test_formats_with_previous_none(self): - build = get_build_config({'formats': ['none']}, {}) + build = get_build_config({'formats': ['none']}) build.validate() assert build.formats == [] def test_formats_can_be_empty(self): - build = get_build_config({'formats': []}, {}) + build = get_build_config({'formats': []}) build.validate() assert build.formats == [] def test_all_valid_formats(self): build = get_build_config( {'formats': ['pdf', 'htmlzip', 'epub']}, - {}, ) build.validate() assert build.formats == ['pdf', 'htmlzip', 'epub'] @@ -435,7 +423,6 @@ class TestValidateFormats(object): def test_cant_have_none_as_format(self): build = get_build_config( {'formats': ['htmlzip', None]}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -445,7 +432,6 @@ class TestValidateFormats(object): def test_formats_have_only_allowed_values(self): build = get_build_config( {'formats': ['htmlzip', 'csv']}, - {}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -453,7 +439,7 @@ class TestValidateFormats(object): assert excinfo.value.code == INVALID_CHOICE def test_only_list_type(self): - build = get_build_config({'formats': 'no-list'}, {}) + build = get_build_config({'formats': 'no-list'}) with raises(InvalidConfig) as excinfo: build.validate() assert excinfo.value.key == 'format' @@ -553,7 +539,7 @@ class TestValidateBuild(object): def test_use_conda_default_false(): - build = get_build_config({}, {}) + build = get_build_config({}) build.validate() assert build.conda is None @@ -561,7 +547,6 @@ def test_use_conda_default_false(): def test_use_conda_respects_config(): build = get_build_config( {'conda': {}}, - {}, ) build.validate() assert isinstance(build.conda, Conda) @@ -571,7 +556,6 @@ def test_validates_conda_file(tmpdir): apply_fs(tmpdir, {'environment.yml': ''}) build = get_build_config( {'conda': {'file': 'environment.yml'}}, - {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -580,7 +564,7 @@ def test_validates_conda_file(tmpdir): def test_requirements_file_empty(): - build = get_build_config({}, {}) + build = get_build_config({}) build.validate() assert build.python.requirements is None @@ -603,7 +587,6 @@ def test_requirements_file_respects_configuration(tmpdir): apply_fs(tmpdir, {'requirements.txt': ''}) build = get_build_config( {'requirements_file': 'requirements.txt'}, - {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -613,7 +596,6 @@ def test_requirements_file_respects_configuration(tmpdir): def test_requirements_file_is_null(tmpdir): build = get_build_config( {'requirements_file': None}, - {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -623,7 +605,6 @@ def test_requirements_file_is_null(tmpdir): def test_requirements_file_is_blank(tmpdir): build = get_build_config( {'requirements_file': ''}, - {}, source_file=str(tmpdir.join('readthedocs.yml')), ) build.validate() @@ -654,7 +635,7 @@ def test_load_calls_validate(tmpdir): def test_raise_config_not_supported(): - build = get_build_config({}, {}) + build = get_build_config({}) build.validate() with raises(ConfigOptionNotSupportedError) as excinfo: build.redirects diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 2b2c6ceb4..95cdefb54 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -88,8 +88,6 @@ class LoadConfigTests(TestCase): env_config={ 'allow_v2': mock.ANY, 'build': {'image': 'readthedocs/build:1.0'}, - 'output_base': '', - 'name': mock.ANY, 'defaults': { 'install_project': self.project.install_project, 'formats': [ From 23b20cdb8729597c7eae8235d3ff4b5da47fe2a7 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 16:42:58 +0100 Subject: [PATCH 05/65] Do not run py27 on tox --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 4f96fee19..ffac6f78c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ [tox] minversion=2.9.0 -envlist = py{27,36},lint,docs +envlist = py36,lint,docs skipsdist = True [travis] python = - 2.7: py27 3.6: py36, codecov [testenv] From 678027c5f6c32ef156d91f13d9327905fed014dd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 16:43:31 +0100 Subject: [PATCH 06/65] Do not run py27 on Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6743bf289..bce5b2f37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - 2.7 - 3.6 env: - ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz From f7dea72b86f09e33014c1fcd7e113eaf875cad2a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 16:56:33 +0100 Subject: [PATCH 07/65] pyupgrade 1.11.0 on all code pyupgrade --py3-plus --py3-only (*.py) --- readthedocs/api/base.py | 12 ++-- readthedocs/api/utils.py | 2 +- readthedocs/builds/forms.py | 4 +- readthedocs/builds/managers.py | 2 +- readthedocs/builds/migrations/0001_initial.py | 4 +- readthedocs/builds/models.py | 18 +++--- readthedocs/builds/syncers.py | 4 +- readthedocs/builds/version_slug.py | 4 +- readthedocs/builds/views.py | 8 +-- readthedocs/config/config.py | 14 ++--- readthedocs/config/tests/test_config.py | 16 ++--- readthedocs/config/tests/test_parser.py | 18 +++--- readthedocs/config/tests/test_validation.py | 22 +++---- readthedocs/config/validation.py | 8 +-- readthedocs/core/adapters.py | 6 +- readthedocs/core/forms.py | 10 +-- .../commands/import_github_language.py | 4 +- readthedocs/core/middleware.py | 8 +-- readthedocs/core/mixins.py | 6 +- readthedocs/core/permissions.py | 2 +- readthedocs/core/resolver.py | 14 ++--- readthedocs/core/settings.py | 2 +- readthedocs/core/static.py | 2 +- readthedocs/core/symlink.py | 6 +- readthedocs/core/tests/test_signals.py | 2 +- readthedocs/core/utils/__init__.py | 2 +- readthedocs/core/utils/tasks/public.py | 2 +- readthedocs/core/utils/tasks/retrieve.py | 2 +- readthedocs/core/views/__init__.py | 4 +- readthedocs/core/views/hooks.py | 8 +-- readthedocs/core/views/serve.py | 2 +- readthedocs/doc_builder/backends/mkdocs.py | 2 +- readthedocs/doc_builder/backends/sphinx.py | 24 +++---- readthedocs/doc_builder/base.py | 2 +- readthedocs/doc_builder/environments.py | 26 ++++---- readthedocs/doc_builder/exceptions.py | 2 +- .../doc_builder/python_environments.py | 8 +-- readthedocs/gold/forms.py | 8 +-- readthedocs/gold/models.py | 2 +- readthedocs/gold/views.py | 10 +-- readthedocs/integrations/admin.py | 6 +- readthedocs/integrations/models.py | 30 ++++----- readthedocs/notifications/backends.py | 2 +- readthedocs/notifications/forms.py | 4 +- readthedocs/notifications/notification.py | 6 +- readthedocs/notifications/storages.py | 4 +- readthedocs/notifications/views.py | 8 +-- .../oauth/migrations/0003_move_github.py | 4 +- readthedocs/oauth/models.py | 2 +- readthedocs/oauth/notifications.py | 4 +- readthedocs/oauth/services/base.py | 2 +- readthedocs/oauth/services/bitbucket.py | 4 +- readthedocs/payments/forms.py | 6 +- readthedocs/payments/mixins.py | 4 +- readthedocs/projects/admin.py | 6 +- readthedocs/projects/forms.py | 62 +++++++++---------- .../migrations/0007_migrate_canonical_data.py | 4 +- .../migrations/0010_migrate_domain_data.py | 4 +- .../migrations/0016_build-queue-name.py | 2 +- readthedocs/projects/models.py | 26 ++++---- readthedocs/projects/tasks.py | 10 +-- readthedocs/projects/validators.py | 8 +-- readthedocs/projects/version_handling.py | 2 +- readthedocs/projects/views/base.py | 12 ++-- readthedocs/projects/views/mixins.py | 4 +- readthedocs/projects/views/private.py | 23 +++---- readthedocs/projects/views/public.py | 10 +-- readthedocs/redirects/models.py | 2 +- readthedocs/restapi/permissions.py | 4 +- readthedocs/restapi/serializers.py | 16 ++--- readthedocs/restapi/views/footer_views.py | 4 +- readthedocs/restapi/views/integrations.py | 10 +-- readthedocs/rtd_tests/base.py | 14 ++--- readthedocs/rtd_tests/files/conf.py | 6 +- .../fixtures/sample_repo/source/conf.py | 12 ++-- readthedocs/rtd_tests/mocks/environment.py | 2 +- readthedocs/rtd_tests/mocks/mock_api.py | 6 +- readthedocs/rtd_tests/tests/test_api.py | 47 +++++++------- readthedocs/rtd_tests/tests/test_backend.py | 26 ++++---- .../rtd_tests/tests/test_build_config.py | 2 +- readthedocs/rtd_tests/tests/test_celery.py | 4 +- .../tests/test_config_integration.py | 2 +- .../rtd_tests/tests/test_doc_building.py | 56 ++++++++--------- readthedocs/rtd_tests/tests/test_extend.py | 2 +- .../rtd_tests/tests/test_integrations.py | 32 +++++----- .../rtd_tests/tests/test_notifications.py | 4 +- .../rtd_tests/tests/test_privacy_urls.py | 10 +-- readthedocs/rtd_tests/tests/test_project.py | 2 +- .../rtd_tests/tests/test_project_forms.py | 8 +-- .../rtd_tests/tests/test_project_symlinks.py | 30 ++++----- .../rtd_tests/tests/test_project_views.py | 15 +++-- readthedocs/rtd_tests/tests/test_resolver.py | 2 +- readthedocs/rtd_tests/tests/test_urls.py | 2 +- .../tests/test_version_commit_name.py | 30 ++++----- readthedocs/search/indexes.py | 4 +- readthedocs/search/parse_json.py | 6 +- readthedocs/search/tests/test_views.py | 2 +- readthedocs/search/utils.py | 4 +- readthedocs/search/views.py | 2 +- readthedocs/settings/base.py | 2 +- readthedocs/settings/dev.py | 2 +- readthedocs/vcs_support/backends/bzr.py | 4 +- readthedocs/vcs_support/backends/git.py | 10 +-- readthedocs/vcs_support/backends/hg.py | 4 +- readthedocs/vcs_support/backends/svn.py | 6 +- readthedocs/vcs_support/base.py | 6 +- readthedocs/vcs_support/utils.py | 4 +- 107 files changed, 479 insertions(+), 488 deletions(-) diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py index 73b64e431..8e90c279b 100644 --- a/readthedocs/api/base.py +++ b/readthedocs/api/base.py @@ -34,7 +34,7 @@ class ProjectResource(ModelResource): users = fields.ToManyField('readthedocs.api.base.UserResource', 'users') - class Meta(object): + class Meta: include_absolute_url = True allowed_methods = ['get', 'post', 'put'] queryset = Project.objects.api() @@ -48,7 +48,7 @@ class ProjectResource(ModelResource): def get_object_list(self, request): self._meta.queryset = Project.objects.api(user=request.user) - return super(ProjectResource, self).get_object_list(request) + return super().get_object_list(request) def dehydrate(self, bundle): bundle.data['downloads'] = bundle.obj.get_downloads() @@ -98,7 +98,7 @@ class VersionResource(ModelResource): project = fields.ForeignKey(ProjectResource, 'project', full=True) - class Meta(object): + class Meta: allowed_methods = ['get', 'put', 'post'] always_return_data = True queryset = Version.objects.api() @@ -112,7 +112,7 @@ class VersionResource(ModelResource): def get_object_list(self, request): self._meta.queryset = Version.objects.api(user=request.user) - return super(VersionResource, self).get_object_list(request) + return super().get_object_list(request) def build_version(self, request, **kwargs): project = get_object_or_404(Project, slug=kwargs['project_slug']) @@ -145,7 +145,7 @@ class FileResource(ModelResource): project = fields.ForeignKey(ProjectResource, 'project', full=True) - class Meta(object): + class Meta: allowed_methods = ['get', 'post'] queryset = ImportedFile.objects.all() excludes = ['md5', 'slug'] @@ -190,7 +190,7 @@ class UserResource(ModelResource): """Read-only API resource for User model.""" - class Meta(object): + class Meta: allowed_methods = ['get'] queryset = User.objects.all() fields = ['username', 'id'] diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py index 1daa2deb9..c15cb02ec 100644 --- a/readthedocs/api/utils.py +++ b/readthedocs/api/utils.py @@ -18,7 +18,7 @@ class PostAuthentication(BasicAuthentication): """Require HTTP Basic authentication for any method other than GET.""" def is_authenticated(self, request, **kwargs): - val = super(PostAuthentication, self).is_authenticated(request, + val = super().is_authenticated(request, **kwargs) if request.method == "GET": return True diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index 74fe0dba5..6e5e94b50 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -17,7 +17,7 @@ from readthedocs.core.utils import trigger_build class VersionForm(forms.ModelForm): - class Meta(object): + class Meta: model = Version fields = ['active', 'privacy_level', 'tags'] @@ -38,7 +38,7 @@ class VersionForm(forms.ModelForm): return project.default_version == self.instance.slug def save(self, commit=True): - obj = super(VersionForm, self).save(commit=commit) + obj = super().save(commit=commit) if obj.active and not obj.built and not obj.uploaded: trigger_build(project=obj.project, version=obj) return obj diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py index 9ef1b2836..bd9316c3e 100644 --- a/readthedocs/builds/managers.py +++ b/readthedocs/builds/managers.py @@ -32,7 +32,7 @@ class VersionManagerBase(models.Manager): VersionQuerySet, VersionQuerySet._default_class # pylint: disable=protected-access ) - return super(VersionManagerBase, cls).from_queryset(queryset_class, class_name) + return super().from_queryset(queryset_class, class_name) def create_stable(self, **kwargs): defaults = { diff --git a/readthedocs/builds/migrations/0001_initial.py b/readthedocs/builds/migrations/0001_initial.py index 32e6e0eb1..73ff3a648 100644 --- a/readthedocs/builds/migrations/0001_initial.py +++ b/readthedocs/builds/migrations/0001_initial.py @@ -77,10 +77,10 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='version', - unique_together=set([('project', 'slug')]), + unique_together={('project', 'slug')}, ), migrations.AlterIndexTogether( name='build', - index_together=set([('version', 'state', 'type')]), + index_together={('version', 'state', 'type')}, ), ] diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 936789752..b27b7e145 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -114,7 +114,7 @@ class Version(models.Model): objects = VersionManager.from_queryset(VersionQuerySet)() - class Meta(object): + class Meta: unique_together = [('project', 'slug')] ordering = ['-verbose_name'] permissions = ( @@ -203,7 +203,7 @@ class Version(models.Model): def save(self, *args, **kwargs): # pylint: disable=arguments-differ """Add permissions to the Version for all owners on save.""" from readthedocs.projects import tasks - obj = super(Version, self).save(*args, **kwargs) + obj = super().save(*args, **kwargs) for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( @@ -216,7 +216,7 @@ class Version(models.Model): broadcast(type='app', task=tasks.clear_artifacts, args=[self.get_artifact_paths()]) broadcast( type='app', task=tasks.symlink_project, args=[self.project.pk]) - super(Version, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) @property def identifier_friendly(self): @@ -434,7 +434,7 @@ class APIVersion(Version): del kwargs[key] except KeyError: pass - super(APIVersion, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, *args, **kwargs): return 0 @@ -479,13 +479,13 @@ class Build(models.Model): CONFIG_KEY = '__config' - class Meta(object): + class Meta: ordering = ['-date'] get_latest_by = 'date' index_together = [['version', 'state', 'type']] def __init__(self, *args, **kwargs): - super(Build, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._config_changed = False @property @@ -549,7 +549,7 @@ class Build(models.Model): self._config and self._config == previous.config): previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} - super(Build, self).save(*args, **kwargs) + super().save(*args, **kwargs) self._config_changed = False def __str__(self): @@ -571,7 +571,7 @@ class Build(models.Model): return self.state == BUILD_STATE_FINISHED -class BuildCommandResultMixin(object): +class BuildCommandResultMixin: """ Mixin for common command result methods/properties. @@ -611,7 +611,7 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): start_time = models.DateTimeField(_('Start time')) end_time = models.DateTimeField(_('End time')) - class Meta(object): + class Meta: ordering = ['start_time'] get_latest_by = 'start_time' diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py index 0ac0c6ded..cf5a9a90e 100644 --- a/readthedocs/builds/syncers.py +++ b/readthedocs/builds/syncers.py @@ -22,7 +22,7 @@ from readthedocs.core.utils import safe_makedirs log = logging.getLogger(__name__) -class BaseSyncer(object): +class BaseSyncer: """A base object for syncers and pullers""" @@ -64,7 +64,7 @@ class RemoteSyncer(BaseSyncer): if app_servers: log.info("Remote Copy %s to %s on %s", path, target, app_servers) for server in app_servers: - mkdir_cmd = ("ssh %s@%s mkdir -p %s" % (sync_user, server, target)) + mkdir_cmd = ("ssh {}@{} mkdir -p {}".format(sync_user, server, target)) ret = os.system(mkdir_cmd) if ret != 0: log.debug("Copy error to app servers: cmd=%s", mkdir_cmd) diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py index 61622369d..7eb71f827 100644 --- a/readthedocs/builds/version_slug.py +++ b/readthedocs/builds/version_slug.py @@ -72,7 +72,7 @@ class VersionSlugField(models.CharField): raise ValueError("missing 'populate_from' argument") else: self._populate_from = populate_from - super(VersionSlugField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_queryset(self, model_cls, slug_field): # pylint: disable=protected-access @@ -180,6 +180,6 @@ class VersionSlugField(models.CharField): return value def deconstruct(self): - name, path, args, kwargs = super(VersionSlugField, self).deconstruct() + name, path, args, kwargs = super().deconstruct() kwargs['populate_from'] = self._populate_from return name, path, args, kwargs diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py index 7cbe310fb..680bd271c 100644 --- a/readthedocs/builds/views.py +++ b/readthedocs/builds/views.py @@ -33,7 +33,7 @@ from readthedocs.projects.models import Project log = logging.getLogger(__name__) -class BuildBase(object): +class BuildBase: model = Build def get_queryset(self): @@ -49,7 +49,7 @@ class BuildBase(object): return queryset -class BuildTriggerMixin(object): +class BuildTriggerMixin: @method_decorator(login_required) def post(self, request, project_slug): @@ -85,7 +85,7 @@ class BuildTriggerMixin(object): class BuildList(BuildBase, BuildTriggerMixin, ListView): def get_context_data(self, **kwargs): - context = super(BuildList, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) active_builds = self.get_queryset().exclude(state='finished' ).values('id') @@ -104,7 +104,7 @@ class BuildDetail(BuildBase, DetailView): pk_url_kwarg = 'build_pk' def get_context_data(self, **kwargs): - context = super(BuildDetail, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['project'] = self.project return context diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 4b7c48f56..ac6b3db99 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -85,7 +85,7 @@ class ConfigError(Exception): def __init__(self, message, code): self.code = code - super(ConfigError, self).__init__(message) + super().__init__(message) class ConfigOptionNotSupportedError(ConfigError): @@ -97,7 +97,7 @@ class ConfigOptionNotSupportedError(ConfigError): template = ( 'The "{}" configuration option is not supported in this version' ) - super(ConfigOptionNotSupportedError, self).__init__( + super().__init__( template.format(self.configuration), CONFIG_NOT_SUPPORTED ) @@ -118,10 +118,10 @@ class InvalidConfig(ConfigError): code=code, error=error_message, ) - super(InvalidConfig, self).__init__(message, code=code) + super().__init__(message, code=code) -class BuildConfigBase(object): +class BuildConfigBase: """ Config that handles the build of one particular documentation. @@ -229,7 +229,7 @@ class BuildConfigBase(object): @property def python_interpreter(self): ver = self.python_full_version - return 'python{0}'.format(ver) + return 'python{}'.format(ver) @property def python_full_version(self): @@ -481,7 +481,7 @@ class BuildConfigV1(BuildConfigBase): # Try to convert strings to an int first, to catch '2', then # a float, to catch '2.7' version = raw_python['version'] - if isinstance(version, six.string_types): + if isinstance(version, str): try: version = int(version) except ValueError: @@ -729,7 +729,7 @@ class BuildConfigV2(BuildConfigBase): python = {} with self.catch_validation_error('python.version'): version = self.pop_config('python.version', 3) - if isinstance(version, six.string_types): + if isinstance(version, str): try: version = int(version) except ValueError: diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index f623881a6..29b776d0f 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -268,7 +268,7 @@ def test_python_pip_install_default(): assert build.python.install_with_pip is False -class TestValidatePythonExtraRequirements(object): +class TestValidatePythonExtraRequirements: def test_it_defaults_to_list(self): build = get_build_config({'python': {}}, get_env_config()) @@ -302,7 +302,7 @@ class TestValidatePythonExtraRequirements(object): validate_string.assert_any_call('tests') -class TestValidateUseSystemSitePackages(object): +class TestValidateUseSystemSitePackages: def test_it_defaults_to_false(self): build = get_build_config({'python': {}}, get_env_config()) @@ -330,7 +330,7 @@ class TestValidateUseSystemSitePackages(object): validate_bool.assert_any_call('to-validate') -class TestValidateSetupPyInstall(object): +class TestValidateSetupPyInstall: def test_it_defaults_to_false(self): build = get_build_config({'python': {}}, get_env_config()) @@ -358,7 +358,7 @@ class TestValidateSetupPyInstall(object): validate_bool.assert_any_call('to-validate') -class TestValidatePythonVersion(object): +class TestValidatePythonVersion: def test_it_defaults_to_a_valid_version(self): build = get_build_config({'python': {}}, get_env_config()) @@ -458,7 +458,7 @@ class TestValidatePythonVersion(object): assert build.python.version == value -class TestValidateFormats(object): +class TestValidateFormats: def test_it_defaults_to_empty(self): build = get_build_config({}, get_env_config()) @@ -537,7 +537,7 @@ def test_valid_build_config(): assert build.output_base -class TestValidateBase(object): +class TestValidateBase: def test_it_validates_to_abspath(self, tmpdir): apply_fs(tmpdir, {'configs': minimal_config, 'docs': {}}) @@ -586,7 +586,7 @@ class TestValidateBase(object): assert excinfo.value.code == INVALID_PATH -class TestValidateBuild(object): +class TestValidateBuild: def test_it_fails_if_build_is_invalid_option(self, tmpdir): apply_fs(tmpdir, minimal_config) @@ -842,7 +842,7 @@ def test_as_dict(tmpdir): assert build.as_dict() == expected_dict -class TestBuildConfigV2(object): +class TestBuildConfigV2: def get_build_config( self, config, env_config=None, source_file='readthedocs.yml'): diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py index 5c37c3c5c..4cbe457bb 100644 --- a/readthedocs/config/tests/test_parser.py +++ b/readthedocs/config/tests/test_parser.py @@ -8,57 +8,57 @@ from readthedocs.config.parser import ParseError, parse def test_parse_empty_config_file(): - buf = StringIO(u'') + buf = StringIO('') with raises(ParseError): parse(buf) def test_parse_invalid_yaml(): - buf = StringIO(u'- - !asdf') + buf = StringIO('- - !asdf') with raises(ParseError): parse(buf) def test_parse_bad_type(): - buf = StringIO(u'Hello') + buf = StringIO('Hello') with raises(ParseError): parse(buf) def test_parse_single_config(): - buf = StringIO(u'base: path') + buf = StringIO('base: path') config = parse(buf) assert isinstance(config, dict) assert config['base'] == 'path' def test_parse_null_value(): - buf = StringIO(u'base: null') + buf = StringIO('base: null') config = parse(buf) assert config['base'] is None def test_parse_empty_value(): - buf = StringIO(u'base:') + buf = StringIO('base:') config = parse(buf) assert config['base'] is None def test_parse_empty_string_value(): - buf = StringIO(u'base: ""') + buf = StringIO('base: ""') config = parse(buf) assert config['base'] == '' def test_parse_empty_list(): - buf = StringIO(u'base: []') + buf = StringIO('base: []') config = parse(buf) assert config['base'] == [] def test_do_not_parse_multiple_configs_in_one_file(): buf = StringIO( - u''' + ''' base: path --- base: other_path diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py index 8c2519570..6437acfbf 100644 --- a/readthedocs/config/tests/test_validation.py +++ b/readthedocs/config/tests/test_validation.py @@ -14,7 +14,7 @@ from readthedocs.config.validation import ( validate_path, validate_string) -class TestValidateBool(object): +class TestValidateBool: def test_it_accepts_true(self): assert validate_bool(True) is True @@ -33,7 +33,7 @@ class TestValidateBool(object): assert excinfo.value.code == INVALID_BOOL -class TestValidateChoice(object): +class TestValidateChoice: def test_it_accepts_valid_choice(self): result = validate_choice('choice', ('choice', 'another_choice')) @@ -49,7 +49,7 @@ class TestValidateChoice(object): assert excinfo.value.code == INVALID_CHOICE -class TestValidateList(object): +class TestValidateList: def test_it_accepts_list_types(self): result = validate_list(['choice', 'another_choice']) @@ -74,12 +74,12 @@ class TestValidateList(object): assert excinfo.value.code == INVALID_LIST -class TestValidateDirectory(object): +class TestValidateDirectory: def test_it_uses_validate_path(self, tmpdir): patcher = patch('readthedocs.config.validation.validate_path') with patcher as validate_path: - path = text_type(tmpdir.mkdir('a directory')) + path = str(tmpdir.mkdir('a directory')) validate_path.return_value = path validate_directory(path, str(tmpdir)) validate_path.assert_called_with(path, str(tmpdir)) @@ -91,7 +91,7 @@ class TestValidateDirectory(object): assert excinfo.value.code == INVALID_DIRECTORY -class TestValidateFile(object): +class TestValidateFile: def test_it_uses_validate_path(self, tmpdir): patcher = patch('readthedocs.config.validation.validate_path') @@ -110,7 +110,7 @@ class TestValidateFile(object): assert excinfo.value.code == INVALID_FILE -class TestValidatePath(object): +class TestValidatePath: def test_it_accepts_relative_path(self, tmpdir): tmpdir.mkdir('a directory') @@ -140,15 +140,15 @@ class TestValidatePath(object): assert excinfo.value.code == INVALID_PATH -class TestValidateString(object): +class TestValidateString: def test_it_accepts_unicode(self): - result = validate_string(u'Unicöde') - assert isinstance(result, text_type) + result = validate_string('Unicöde') + assert isinstance(result, str) def test_it_accepts_nonunicode(self): result = validate_string('Unicode') - assert isinstance(result, text_type) + assert isinstance(result, str) def test_it_rejects_float(self): with raises(ValidationError) as excinfo: diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index ab9164f33..b1b42fc67 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -41,12 +41,12 @@ class ValidationError(Exception): if format_kwargs is not None: defaults.update(format_kwargs) message = self.messages[code].format(**defaults) - super(ValidationError, self).__init__(message) + super().__init__(message) def validate_list(value): """Check if ``value`` is an iterable.""" - if isinstance(value, (dict, string_types)): + if isinstance(value, (dict, (str,))): raise ValidationError(value, INVALID_LIST) if not hasattr(value, '__iter__'): raise ValidationError(value, INVALID_LIST) @@ -113,6 +113,6 @@ def validate_path(value, base_path): def validate_string(value): """Check that ``value`` is a string type.""" - if not isinstance(value, string_types): + if not isinstance(value, str): raise ValidationError(value, INVALID_STRING) - return text_type(value) + return str(value) diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py index 170f8954e..5bfbf6d5c 100644 --- a/readthedocs/core/adapters.py +++ b/readthedocs/core/adapters.py @@ -26,7 +26,7 @@ class AccountAdapter(DefaultAccountAdapter): def send_mail(self, template_prefix, email, context): subject = render_to_string( - '{0}_subject.txt'.format(template_prefix), context + '{}_subject.txt'.format(template_prefix), context ) subject = " ".join(subject.splitlines()).strip() subject = self.format_email_subject(subject) @@ -47,7 +47,7 @@ class AccountAdapter(DefaultAccountAdapter): send_email( recipient=email, subject=subject, - template='{0}_message.txt'.format(template_prefix), - template_html='{0}_message.html'.format(template_prefix), + template='{}_message.txt'.format(template_prefix), + template_html='{}_message.html'.format(template_prefix), context=context ) diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py index bc0622865..fdc93ffe2 100644 --- a/readthedocs/core/forms.py +++ b/readthedocs/core/forms.py @@ -21,13 +21,13 @@ class UserProfileForm(forms.ModelForm): first_name = CharField(label=_('First name'), required=False, max_length=30) last_name = CharField(label=_('Last name'), required=False, max_length=30) - class Meta(object): + class Meta: model = UserProfile # Don't allow users edit someone else's user page fields = ['first_name', 'last_name', 'homepage'] def __init__(self, *args, **kwargs): - super(UserProfileForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) try: self.fields['first_name'].initial = self.instance.user.first_name self.fields['last_name'].initial = self.instance.user.last_name @@ -37,7 +37,7 @@ class UserProfileForm(forms.ModelForm): def save(self, commit=True): first_name = self.cleaned_data.pop('first_name', None) last_name = self.cleaned_data.pop('last_name', None) - profile = super(UserProfileForm, self).save(commit=commit) + profile = super().save(commit=commit) if commit: user = profile.user user.first_name = first_name @@ -52,7 +52,7 @@ class UserDeleteForm(forms.ModelForm): help_text=_('Please type your username to confirm.'), ) - class Meta(object): + class Meta: model = User fields = ['username'] @@ -66,7 +66,7 @@ class UserDeleteForm(forms.ModelForm): class UserAdvertisingForm(forms.ModelForm): - class Meta(object): + class Meta: model = UserProfile fields = ['allow_ads'] diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index ef2945ea5..f53a80887 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -53,7 +53,7 @@ class Command(BaseCommand): print('No GitHub repo for %s' % repo_url) continue - cache_key = '%s-%s' % (user, repo) + cache_key = '{}-{}'.format(user, repo) top_lang = cache.get(cache_key, None) if not top_lang: url = 'https://api.github.com/repos/{user}/{repo}/languages'.format( @@ -73,7 +73,7 @@ class Command(BaseCommand): print('Cached top_lang: %s' % top_lang) if top_lang in PL_DICT: slug = PL_DICT[top_lang] - print('Setting %s to %s' % (repo_url, slug)) + print('Setting {} to {}'.format(repo_url, slug)) Project.objects.filter(pk=project.pk).update(programming_language=slug) else: print('Language unknown: %s' % top_lang) diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py index 262fcb064..4556d92ff 100644 --- a/readthedocs/core/middleware.py +++ b/readthedocs/core/middleware.py @@ -19,7 +19,7 @@ from readthedocs.projects.models import Domain, Project log = logging.getLogger(__name__) -LOG_TEMPLATE = u"(Middleware) {msg} [{host}{path}]" +LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]" SUBDOMAIN_URLCONF = getattr( settings, 'SUBDOMAIN_URLCONF', @@ -112,7 +112,7 @@ class SubdomainMiddleware(MiddlewareMixin): cache.set(host, slug, 60 * 60) # Cache the slug -> host mapping permanently. log.info(LOG_TEMPLATE.format( - msg='CNAME cached: %s->%s' % (slug, host), + msg='CNAME cached: {}->{}'.format(slug, host), **log_kwargs)) request.slug = slug request.urlconf = SUBDOMAIN_URLCONF @@ -240,11 +240,11 @@ class FooterNoSessionMiddleware(SessionMiddleware): # Hack request.session otherwise the Authentication middleware complains. request.session = {} return - super(FooterNoSessionMiddleware, self).process_request(request) + super().process_request(request) def process_response(self, request, response): for url in self.IGNORE_URLS: if (request.path_info.startswith(url) and settings.SESSION_COOKIE_NAME not in request.COOKIES): return response - return super(FooterNoSessionMiddleware, self).process_response(request, response) + return super().process_response(request, response) diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py index 7655db20b..45bf2be75 100644 --- a/readthedocs/core/mixins.py +++ b/readthedocs/core/mixins.py @@ -12,13 +12,13 @@ class ListViewWithForm(ListView): """List view that also exposes a create form""" def get_context_data(self, **kwargs): - context = super(ListViewWithForm, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['form'] = self.get_form(data=None, files=None) return context -class LoginRequiredMixin(object): +class LoginRequiredMixin: @method_decorator(login_required) def dispatch(self, *args, **kwargs): - return super(LoginRequiredMixin, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py index c8a7fe682..ad2427b9b 100644 --- a/readthedocs/core/permissions.py +++ b/readthedocs/core/permissions.py @@ -5,7 +5,7 @@ from __future__ import absolute_import from readthedocs.core.utils.extend import SettingsOverrideObject -class AdminPermissionBase(object): +class AdminPermissionBase: @classmethod def is_admin(cls, user, project): diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index 43fce68b3..a17e83216 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -10,7 +10,7 @@ from readthedocs.projects.constants import PRIVATE, PUBLIC from readthedocs.core.utils.extend import SettingsOverrideObject -class ResolverBase(object): +class ResolverBase: """ Read the Docs URL Resolver. @@ -59,17 +59,17 @@ class ResolverBase(object): # the path should always have a subdomain or CNAME domain # pylint: disable=unused-argument if subdomain or cname or (self._use_subdomain()): - url = u'/' + url = '/' else: - url = u'/docs/{project_slug}/' + url = '/docs/{project_slug}/' if subproject_slug: - url += u'projects/{subproject_slug}/' + url += 'projects/{subproject_slug}/' if single_version: - url += u'{filename}' + url += '{filename}' else: - url += u'{language}/{version_slug}/{filename}' + url += '{language}/{version_slug}/{filename}' return url.format( project_slug=project_slug, filename=filename, @@ -212,7 +212,7 @@ class ResolverBase(object): if self._use_subdomain(): project = self._get_canonical_project(project) subdomain_slug = project.slug.replace('_', '-') - return "%s.%s" % (subdomain_slug, public_domain) + return "{}.{}".format(subdomain_slug, public_domain) def _get_project_custom_domain(self, project): return project.domains.filter(canonical=True).first() diff --git a/readthedocs/core/settings.py b/readthedocs/core/settings.py index d66c6d02d..b758cb57f 100644 --- a/readthedocs/core/settings.py +++ b/readthedocs/core/settings.py @@ -6,7 +6,7 @@ import inspect import sys -class Settings(object): +class Settings: """Class-based settings wrapper.""" diff --git a/readthedocs/core/static.py b/readthedocs/core/static.py index 89cd88387..b7ec2b796 100644 --- a/readthedocs/core/static.py +++ b/readthedocs/core/static.py @@ -15,4 +15,4 @@ class SelectiveFileSystemFinder(FileSystemFinder): def list(self, ignore_patterns): ignore_patterns.extend(['epub', 'pdf', 'htmlzip', 'json', 'man']) - return super(SelectiveFileSystemFinder, self).list(ignore_patterns) + return super().list(ignore_patterns) diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index ec110cf6a..dcaad60bc 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -77,7 +77,7 @@ from readthedocs.projects.models import Domain log = logging.getLogger(__name__) -class Symlink(object): +class Symlink: """Base class for symlinking of projects.""" @@ -177,7 +177,7 @@ class Symlink(object): def remove_symlink_cname(self, domain): """Remove CNAME symlink.""" - log_msg = "Removing symlink for CNAME {0}".format(domain.domain) + log_msg = "Removing symlink for CNAME {}".format(domain.domain) log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, version='', msg=log_msg)) symlink = os.path.join(self.CNAME_ROOT, domain.domain) @@ -205,7 +205,7 @@ class Symlink(object): from_to[rel.alias] = rel.child.slug subprojects.add(rel.alias) for from_slug, to_slug in list(from_to.items()): - log_msg = "Symlinking subproject: {0} -> {1}".format(from_slug, to_slug) + log_msg = "Symlinking subproject: {} -> {}".format(from_slug, to_slug) log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, version='', msg=log_msg)) symlink = os.path.join(self.subproject_root, from_slug) diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py index c38705a1f..6c906c9e8 100644 --- a/readthedocs/core/tests/test_signals.py +++ b/readthedocs/core/tests/test_signals.py @@ -8,7 +8,7 @@ from readthedocs.projects.models import Project @pytest.mark.django_db -class TestProjectOrganizationSignal(object): +class TestProjectOrganizationSignal: @pytest.mark.parametrize('model_class', [Project, RemoteOrganization]) def test_project_organization_get_deleted_upon_user_delete(self, model_class): diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py index 1927c98b7..0b8134751 100644 --- a/readthedocs/core/utils/__init__.py +++ b/readthedocs/core/utils/__init__.py @@ -221,7 +221,7 @@ def slugify(value, *args, **kwargs): return value -slugify = allow_lazy(slugify, six.text_type, SafeText) +slugify = allow_lazy(slugify, str, SafeText) def safe_makedirs(directory_name): diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py index 9fb2948ef..1e51fc65b 100644 --- a/readthedocs/core/utils/tasks/public.py +++ b/readthedocs/core/utils/tasks/public.py @@ -119,7 +119,7 @@ class TaskNoPermission(Exception): def __init__(self, task_id, *args, **kwargs): message = 'No permission to access task with id {id}'.format( id=task_id) - super(TaskNoPermission, self).__init__(message, *args, **kwargs) + super().__init__(message, *args, **kwargs) def get_public_task_data(request, task_id): diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py index c96b78237..0aa3e7570 100644 --- a/readthedocs/core/utils/tasks/retrieve.py +++ b/readthedocs/core/utils/tasks/retrieve.py @@ -16,7 +16,7 @@ __all__ = ('TaskNotFound', 'get_task_data') class TaskNotFound(Exception): def __init__(self, task_id, *args, **kwargs): message = 'No public task found with id {id}'.format(id=task_id) - super(TaskNotFound, self).__init__(message, *args, **kwargs) + super().__init__(message, *args, **kwargs) def get_task_data(task_id): diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 6f25e9bd9..85aec548c 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -35,7 +35,7 @@ class HomepageView(TemplateView): def get_context_data(self, **kwargs): """Add latest builds and featured projects.""" - context = super(HomepageView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['featured_list'] = Project.objects.filter(featured=True) context['projects_count'] = Project.objects.count() return context @@ -45,7 +45,7 @@ class SupportView(TemplateView): template_name = 'support.html' def get_context_data(self, **kwargs): - context = super(SupportView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) support_email = getattr(settings, 'SUPPORT_EMAIL', None) if not support_email: support_email = 'support@{domain}'.format( diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 6b00b2d77..3156e3cd4 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -156,14 +156,14 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: - msg = '(URL Build) Build Started: %s [%s]' % ( + msg = '(URL Build) Build Started: {} [{}]'.format( url, ' '.join(built)) log_info(project_slug, msg=msg) ret += msg for project_slug, not_building in list(all_not_building.items()): if not_building: - msg = '(URL Build) Not Building: %s [%s]' % ( + msg = '(URL Build) Not Building: {} [{}]'.format( url, ' '.join(not_building)) log_info(project_slug, msg=msg) ret += msg @@ -300,7 +300,7 @@ def bitbucket_build(request): repository = data['repository'] if not repository['absolute_url']: return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org{0}'.format( + search_url = 'bitbucket.org{}'.format( repository['absolute_url'].rstrip('/') ) elif version == 2: @@ -309,7 +309,7 @@ def bitbucket_build(request): for change in changes] if not data['repository']['full_name']: return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org/{0}'.format( + search_url = 'bitbucket.org/{}'.format( data['repository']['full_name'] ) except (TypeError, ValueError, KeyError): diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 36510440a..d663dc791 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -116,7 +116,7 @@ def redirect_page_with_filename(request, project, subproject, filename): # pyli def _serve_401(request, project): res = render(request, '401.html') res.status_code = 401 - log.debug('Unauthorized access to {0} documentation'.format(project.slug)) + log.debug('Unauthorized access to {} documentation'.format(project.slug)) return res diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 6119673b8..eeb06379c 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -44,7 +44,7 @@ class BaseMkdocs(BaseBuilder): DEFAULT_THEME_NAME = 'mkdocs' def __init__(self, *args, **kwargs): - super(BaseMkdocs, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.old_artifact_path = os.path.join( self.version.project.checkout_path(self.version.slug), self.build_dir) diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index 1d280b8a3..4f2f7b338 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -40,7 +40,7 @@ class BaseSphinx(BaseBuilder): """The parent for most sphinx builders.""" def __init__(self, *args, **kwargs): - super(BaseSphinx, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.config_file = self.config.sphinx.configuration try: if not self.config_file: @@ -172,13 +172,9 @@ class BaseSphinx(BaseBuilder): outfile = codecs.open(self.config_file, encoding='utf-8', mode='a') except (ProjectConfigurationError, IOError): trace = sys.exc_info()[2] - six.reraise( - ProjectConfigurationError, - ProjectConfigurationError( + raise ProjectConfigurationError( ProjectConfigurationError.NOT_FOUND - ), - trace - ) + ).with_traceback(trace) # Append config to project conf file tmpl = template_loader.get_template('doc_builder/conf.py.tmpl') @@ -234,11 +230,11 @@ class HtmlBuilder(BaseSphinx): sphinx_build_dir = '_build/html' def __init__(self, *args, **kwargs): - super(HtmlBuilder, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.sphinx_builder = 'readthedocs' def move(self, **__): - super(HtmlBuilder, self).move() + super().move() # Copy JSON artifacts to its own directory # to keep compatibility with the older builder. json_path = os.path.abspath( @@ -265,7 +261,7 @@ class HtmlDirBuilder(HtmlBuilder): type = 'sphinx_htmldir' def __init__(self, *args, **kwargs): - super(HtmlDirBuilder, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.sphinx_builder = 'readthedocsdirhtml' @@ -273,7 +269,7 @@ class SingleHtmlBuilder(HtmlBuilder): type = 'sphinx_singlehtml' def __init__(self, *args, **kwargs): - super(SingleHtmlBuilder, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.sphinx_builder = 'readthedocssinglehtml' @@ -338,7 +334,7 @@ class LatexBuildCommand(BuildCommand): """Ignore LaTeX exit code if there was file output.""" def run(self): - super(LatexBuildCommand, self).run() + super().run() # Force LaTeX exit code to be a little more optimistic. If LaTeX # reports an output file, let's just assume we're fine. if PDF_RE.search(self.output): @@ -350,7 +346,7 @@ class DockerLatexBuildCommand(DockerBuildCommand): """Ignore LaTeX exit code if there was file output.""" def run(self): - super(DockerLatexBuildCommand, self).run() + super().run() # Force LaTeX exit code to be a little more optimistic. If LaTeX # reports an output file, let's just assume we're fine. if PDF_RE.search(self.output): @@ -395,7 +391,7 @@ class PdfBuilder(BaseSphinx): ['pdflatex', '-interaction=nonstopmode', tex_file] for tex_file in tex_files] # yapf: disable makeindex_cmds = [ - ['makeindex', '-s', 'python.ist', '{0}.idx'.format( + ['makeindex', '-s', 'python.ist', '{}.idx'.format( os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0])] for tex_file in tex_files] # yapf: disable diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py index 83aac0da6..c382fefc3 100644 --- a/readthedocs/doc_builder/base.py +++ b/readthedocs/doc_builder/base.py @@ -26,7 +26,7 @@ def restoring_chdir(fn): return decorator -class BaseBuilder(object): +class BaseBuilder: """ The Base for all Builders. Defines the API for subclasses. diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index b8980ec69..10ba06f5c 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -123,7 +123,7 @@ class BuildCommand(BuildCommandResultMixin): def __str__(self): # TODO do we want to expose the full command here? - output = u'' + output = '' if self.output is not None: output = self.output.encode('utf-8') return '\n'.join([self.get_command(), output]) @@ -178,7 +178,7 @@ class BuildCommand(BuildCommandResultMixin): if self.input_data is not None: cmd_input = self.input_data - if isinstance(cmd_input, six.string_types): + if isinstance(cmd_input, str): cmd_input_bytes = cmd_input.encode('utf-8') else: cmd_input_bytes = cmd_input @@ -336,7 +336,7 @@ class DockerBuildCommand(BuildCommand): r"\[\\\]\^\`\{\|\}\~])") prefix = '' if self.bin_path: - prefix += 'PATH={0}:$PATH '.format(self.bin_path) + prefix += 'PATH={}:$PATH '.format(self.bin_path) return ("/bin/sh -c 'cd {cwd} && {prefix}{cmd}'" .format( cwd=self.cwd, @@ -345,7 +345,7 @@ class DockerBuildCommand(BuildCommand): for part in self.command])))) -class BaseEnvironment(object): +class BaseEnvironment: """ Base environment class. @@ -418,10 +418,10 @@ class BaseEnvironment(object): self.commands.append(build_cmd) if build_cmd.failed: - msg = u'Command {cmd} failed'.format(cmd=build_cmd.get_command()) + msg = 'Command {cmd} failed'.format(cmd=build_cmd.get_command()) if build_cmd.output: - msg += u':\n{out}'.format(out=build_cmd.output) + msg += ':\n{out}'.format(out=build_cmd.output) if warn_only: log.warning(LOG_TEMPLATE.format( @@ -486,7 +486,7 @@ class BuildEnvironment(BaseEnvironment): def __init__(self, project=None, version=None, build=None, config=None, record=True, environment=None, update_on_success=True): - super(BuildEnvironment, self).__init__(project, environment) + super().__init__(project, environment) self.version = version self.build = build self.config = config @@ -562,13 +562,13 @@ class BuildEnvironment(BaseEnvironment): kwargs.update({ 'build_env': self, }) - return super(BuildEnvironment, self).run(*cmd, **kwargs) + return super().run(*cmd, **kwargs) def run_command_class(self, *cmd, **kwargs): # pylint: disable=arguments-differ kwargs.update({ 'build_env': self, }) - return super(BuildEnvironment, self).run_command_class(*cmd, **kwargs) + return super().run_command_class(*cmd, **kwargs) @property def successful(self): @@ -662,7 +662,7 @@ class BuildEnvironment(BaseEnvironment): # Attempt to stop unicode errors on build reporting for key, val in list(self.build.items()): - if isinstance(val, six.binary_type): + if isinstance(val, bytes): self.build[key] = val.decode('utf-8', 'ignore') # We are selective about when we update the build object here @@ -716,7 +716,7 @@ class DockerBuildEnvironment(BuildEnvironment): def __init__(self, *args, **kwargs): self.docker_socket = kwargs.pop('docker_socket', DOCKER_SOCKET) - super(DockerBuildEnvironment, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.client = None self.container = None self.container_name = slugify( @@ -760,7 +760,7 @@ class DockerBuildEnvironment(BuildEnvironment): project=self.project.slug, version=self.version.slug, msg=( - 'Removing stale container {0}' + 'Removing stale container {}' .format(self.container_id) ), ) @@ -824,7 +824,7 @@ class DockerBuildEnvironment(BuildEnvironment): if not all([exc_type, exc_value, tb]): exc_type, exc_value, tb = sys.exc_info() - return super(DockerBuildEnvironment, self).__exit__(exc_type, exc_value, tb) + return super().__exit__(exc_type, exc_value, tb) def get_client(self): """Create Docker client connection.""" diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index ce2ce3d84..80582595b 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -13,7 +13,7 @@ class BuildEnvironmentException(Exception): def __init__(self, message=None, **kwargs): self.status_code = kwargs.pop('status_code', None) or self.status_code or 1 message = message or self.get_default_message() - super(BuildEnvironmentException, self).__init__(message, **kwargs) + super().__init__(message, **kwargs) def get_default_message(self): return self.message diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index fa9bd9f8b..7d85a275f 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -29,7 +29,7 @@ from readthedocs.projects.models import Feature log = logging.getLogger(__name__) -class PythonEnvironment(object): +class PythonEnvironment: """An isolated environment into which Python packages can be installed.""" @@ -73,7 +73,7 @@ class PythonEnvironment(object): getattr(settings, 'USE_PIP_INSTALL', False)): extra_req_param = '' if self.config.python.extra_requirements: - extra_req_param = '[{0}]'.format( + extra_req_param = '[{}]'.format( ','.join(self.config.python.extra_requirements) ) self.build_env.run( @@ -83,7 +83,7 @@ class PythonEnvironment(object): '--ignore-installed', '--cache-dir', self.project.pip_cache_path, - '.{0}'.format(extra_req_param), + '.{}'.format(extra_req_param), cwd=self.checkout_path, bin_path=self.venv_bin(), ) @@ -195,7 +195,7 @@ class PythonEnvironment(object): with open(self.environment_json_path(), 'w') as fpath: # Compatibility for Py2 and Py3. ``io.TextIOWrapper`` expects # unicode but ``json.dumps`` returns str in Py2. - fpath.write(six.text_type(json.dumps(data))) + fpath.write(str(json.dumps(data))) class Virtualenv(PythonEnvironment): diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index 949ab9c4c..fead5e26b 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -24,7 +24,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): :py:class:`StripeResourceMixin` for common operations against the Stripe API. """ - class Meta(object): + class Meta: model = GoldUser fields = ['last_4_card_digits', 'level'] @@ -44,7 +44,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): def clean(self): self.instance.user = self.customer - return super(GoldSubscriptionForm, self).clean() + return super().clean() def validate_stripe(self): subscription = self.get_subscription() @@ -97,7 +97,7 @@ class GoldProjectForm(forms.Form): def __init__(self, active_user, *args, **kwargs): self.user = kwargs.pop('user', None) self.projects = kwargs.pop('projects', None) - super(GoldProjectForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['project'].choices = self.generate_choices(active_user) def generate_choices(self, active_user): @@ -114,7 +114,7 @@ class GoldProjectForm(forms.Form): return project_slug def clean(self): - cleaned_data = super(GoldProjectForm, self).clean() + cleaned_data = super().clean() if self.projects.count() < self.user.num_supported_projects: return cleaned_data diff --git a/readthedocs/gold/models.py b/readthedocs/gold/models.py index b87e0f723..278dc6022 100644 --- a/readthedocs/gold/models.py +++ b/readthedocs/gold/models.py @@ -62,7 +62,7 @@ class GoldUser(models.Model): business_vat_id = models.CharField(max_length=128, null=True, blank=True) def __str__(self): - return 'Gold Level %s for %s' % (self.level, self.user) + return 'Gold Level {} for {}'.format(self.level, self.user) @property def num_supported_projects(self): diff --git a/readthedocs/gold/views.py b/readthedocs/gold/views.py index a0122d828..7e2677e2a 100644 --- a/readthedocs/gold/views.py +++ b/readthedocs/gold/views.py @@ -43,16 +43,16 @@ class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, def get_form(self, data=None, files=None, **kwargs): """Pass in copy of POST data to avoid read only QueryDicts.""" kwargs['customer'] = self.request.user - return super(GoldSubscriptionMixin, self).get_form(data, files, **kwargs) + return super().get_form(data, files, **kwargs) def get_success_url(self, **__): return reverse_lazy('gold_detail') def get_template_names(self): - return ('gold/subscription{0}.html'.format(self.template_name_suffix)) + return ('gold/subscription{}.html'.format(self.template_name_suffix)) def get_context_data(self, **kwargs): - context = super(GoldSubscriptionMixin, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) domains = Domain.objects.filter(project__users=self.request.user) context['domains'] = domains return context @@ -70,7 +70,7 @@ class DetailGoldSubscription(GoldSubscriptionMixin, DetailView): If there is a gold subscription instance, then we show the normal detail page, otherwise show the registration form """ - resp = super(DetailGoldSubscription, self).get(request, *args, **kwargs) + resp = super().get(request, *args, **kwargs) if self.object is None: return HttpResponseRedirect(reverse('gold_subscription')) return resp @@ -94,7 +94,7 @@ class DeleteGoldSubscription(GoldSubscriptionMixin, DeleteView): def post(self, request, *args, **kwargs): """Add success message to delete post.""" - resp = super(DeleteGoldSubscription, self).post(request, *args, **kwargs) + resp = super().post(request, *args, **kwargs) success_message = self.get_success_message({}) if success_message: messages.success(self.request, success_message) diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py index cbeabea02..5fabe23be 100644 --- a/readthedocs/integrations/admin.py +++ b/readthedocs/integrations/admin.py @@ -18,7 +18,7 @@ def pretty_json_field(field, description, include_styles=False): if include_styles: formatter = HtmlFormatter(style='colorful') styles = '' - return mark_safe('
{1}
{2}'.format( + return mark_safe('
{}
{}'.format( 'float: left;', obj.formatted_json(field), styles, @@ -96,11 +96,11 @@ class IntegrationAdmin(admin.ModelAdmin): JSONField doesn't do well with fieldsets for whatever reason. This is just to link to the exchanges. """ - url = urls.reverse('admin:{0}_{1}_changelist'.format( + url = urls.reverse('admin:{}_{}_changelist'.format( HttpExchange._meta.app_label, # pylint: disable=protected-access HttpExchange._meta.model_name, # pylint: disable=protected-access )) - return mark_safe('{3} HTTP transactions'.format( + return mark_safe('{} HTTP transactions'.format( url, 'integrations', obj.pk, diff --git a/readthedocs/integrations/models.py b/readthedocs/integrations/models.py index 7514699ce..032c16c28 100644 --- a/readthedocs/integrations/models.py +++ b/readthedocs/integrations/models.py @@ -73,11 +73,11 @@ class HttpExchangeManager(models.Manager): # headers. HTTP headers are prefixed with `HTTP_`, which we remove, # and because the keys are all uppercase, we'll normalize them to # title case-y hyphen separated values. - request_headers = dict( - (key[5:].title().replace('_', '-'), str(val)) + request_headers = { + key[5:].title().replace('_', '-'): str(val) for (key, val) in list(req.META.items()) - if key.startswith('HTTP_'), - ) # yapf: disable + if key.startswith('HTTP_') + } # yapf: disable request_headers['Content-Type'] = req.content_type # Remove unwanted headers @@ -146,7 +146,7 @@ class HttpExchange(models.Model): objects = HttpExchangeManager() - class Meta(object): + class Meta: ordering = ['-date'] def __str__(self): @@ -191,11 +191,11 @@ class IntegrationQuerySet(models.QuerySet): def _get_subclass(self, integration_type): # Build a mapping of integration_type -> class dynamically - class_map = dict( - (cls.integration_type_id, cls) + class_map = { + cls.integration_type_id: cls for cls in self.model.__subclasses__() - if hasattr(cls, 'integration_type_id'), - ) # yapf: disable + if hasattr(cls, 'integration_type_id') + } # yapf: disable return class_map.get(integration_type) def _get_subclass_replacement(self, original): @@ -215,7 +215,7 @@ class IntegrationQuerySet(models.QuerySet): return new def get(self, *args, **kwargs): - original = super(IntegrationQuerySet, self).get(*args, **kwargs) + original = super().get(*args, **kwargs) return self._get_subclass_replacement(original) def subclass(self, instance): @@ -285,7 +285,7 @@ class GitHubWebhook(Integration): integration_type_id = Integration.GITHUB_WEBHOOK has_sync = True - class Meta(object): + class Meta: proxy = True @property @@ -301,7 +301,7 @@ class BitbucketWebhook(Integration): integration_type_id = Integration.BITBUCKET_WEBHOOK has_sync = True - class Meta(object): + class Meta: proxy = True @property @@ -317,7 +317,7 @@ class GitLabWebhook(Integration): integration_type_id = Integration.GITLAB_WEBHOOK has_sync = True - class Meta(object): + class Meta: proxy = True @property @@ -333,7 +333,7 @@ class GenericAPIWebhook(Integration): integration_type_id = Integration.API_WEBHOOK has_sync = False - class Meta(object): + class Meta: proxy = True def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -346,7 +346,7 @@ class GenericAPIWebhook(Integration): if token is None: token = default_token() self.provider_data = {'token': token} - super(GenericAPIWebhook, self).save(*args, **kwargs) + super().save(*args, **kwargs) @property def token(self): diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py index 28529f1f1..e2552c1bd 100644 --- a/readthedocs/notifications/backends.py +++ b/readthedocs/notifications/backends.py @@ -39,7 +39,7 @@ def send_notification(request, notification): backend.send(notification) -class Backend(object): +class Backend: def __init__(self, request): self.request = request diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py index b65c1c15e..589243085 100644 --- a/readthedocs/notifications/forms.py +++ b/readthedocs/notifications/forms.py @@ -29,12 +29,12 @@ class SendNotificationForm(forms.Form): def __init__(self, *args, **kwargs): self.notification_classes = kwargs.pop('notification_classes', []) - super(SendNotificationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['source'].choices = [(cls.name, cls.name) for cls in self.notification_classes] def clean_source(self): """Get the source class from the class name.""" source = self.cleaned_data['source'] - classes = dict((cls.name, cls) for cls in self.notification_classes) + classes = {cls.name: cls for cls in self.notification_classes} return classes.get(source, None) diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py index d2300e1f4..8329bb91b 100644 --- a/readthedocs/notifications/notification.py +++ b/readthedocs/notifications/notification.py @@ -17,7 +17,7 @@ from . import constants log = logging.getLogger(__name__) -class Notification(object): +class Notification: """ An unsent notification linked to an object. @@ -134,10 +134,10 @@ class SiteNotification(Notification): self.success = success self.reason = reason self.extra_context = extra_context or {} - super(SiteNotification, self).__init__(context_object, request, user) + super().__init__(context_object, request, user) def get_context_data(self): - context = super(SiteNotification, self).get_context_data() + context = super().get_context_data() context.update(self.extra_context) return context diff --git a/readthedocs/notifications/storages.py b/readthedocs/notifications/storages.py index 1755db952..9a98ef763 100644 --- a/readthedocs/notifications/storages.py +++ b/readthedocs/notifications/storages.py @@ -49,7 +49,7 @@ class FallbackUniqueStorage(FallbackStorage): def _get(self, *args, **kwargs): # The database backend for persistent messages doesn't support setting # messages with ``mark_safe``, therefore, we need to do it broadly here. - messages, all_ret = (super(FallbackUniqueStorage, self) + messages, all_ret = (super() ._get(self, *args, **kwargs)) safe_messages = [] @@ -75,7 +75,7 @@ class FallbackUniqueStorage(FallbackStorage): read=False)) if persist_messages.exists(): return - super(FallbackUniqueStorage, self).add(level, message, extra_tags, + super().add(level, message, extra_tags, *args, **kwargs) diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py index 0ba04fae0..2cbdb0959 100644 --- a/readthedocs/notifications/views.py +++ b/readthedocs/notifications/views.py @@ -35,7 +35,7 @@ class SendNotificationView(FormView): The admin posts to this view initially, so detect the send button on form post variables. Drop additional fields if we see the send button. """ - kwargs = super(SendNotificationView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs['notification_classes'] = self.notification_classes if 'send' not in self.request.POST: kwargs.pop('data', None) @@ -44,7 +44,7 @@ class SendNotificationView(FormView): def get_initial(self): """Add selected ids to initial form data.""" - initial = super(SendNotificationView, self).get_initial() + initial = super().get_initial() initial['_selected_action'] = self.request.POST.getlist( admin.ACTION_CHECKBOX_NAME) return initial @@ -63,7 +63,7 @@ class SendNotificationView(FormView): if count == 0: self.message_user("No recipients to send to", level=messages.ERROR) else: - self.message_user("Queued {0} messages".format(count)) + self.message_user("Queued {} messages".format(count)) return HttpResponseRedirect(self.request.get_full_path()) def get_object_recipients(self, obj): @@ -89,7 +89,7 @@ class SendNotificationView(FormView): def get_context_data(self, **kwargs): """Return queryset in context.""" - context = super(SendNotificationView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) recipients = [] for obj in self.get_queryset().all(): recipients.extend(self.get_object_recipients(obj)) diff --git a/readthedocs/oauth/migrations/0003_move_github.py b/readthedocs/oauth/migrations/0003_move_github.py index 40fd4c562..7ff1dbc8e 100644 --- a/readthedocs/oauth/migrations/0003_move_github.py +++ b/readthedocs/oauth/migrations/0003_move_github.py @@ -143,9 +143,9 @@ def forwards_move_repos(apps, schema_editor): new_repo.private = data.get('is_private', False) new_repo.json = json.dumps(data) - clone_urls = dict((location['name'], location['href']) + clone_urls = {location['name']: location['href'] for location - in data.get('links', {}).get('clone', {})) + in data.get('links', {}).get('clone', {})} if new_repo.private: new_repo.clone_url = clone_urls.get('ssh', project.git_url) else: diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py index b93f71b9f..54a02191f 100644 --- a/readthedocs/oauth/models.py +++ b/readthedocs/oauth/models.py @@ -141,7 +141,7 @@ class RemoteRepository(models.Model): objects = RemoteRepositoryQuerySet.as_manager() - class Meta(object): + class Meta: ordering = ['organization__name', 'name'] verbose_name_plural = 'remote repositories' diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py index fc9aefc0c..41b9c78ce 100644 --- a/readthedocs/oauth/notifications.py +++ b/readthedocs/oauth/notifications.py @@ -22,7 +22,7 @@ class AttachWebhookNotification(SiteNotification): } def get_context_data(self): - context = super(AttachWebhookNotification, self).get_context_data() + context = super().get_context_data() project = self.extra_context.get('project') context.update({ 'url_connect_account': reverse( @@ -44,7 +44,7 @@ class InvalidProjectWebhookNotification(SiteNotification): "See the project integrations for more information.") # noqa def get_context_data(self): - context = super(InvalidProjectWebhookNotification, self).get_context_data() + context = super().get_context_data() context.update({ 'url_integrations': reverse( 'projects_integrations', diff --git a/readthedocs/oauth/services/base.py b/readthedocs/oauth/services/base.py index 93064779e..87086a031 100644 --- a/readthedocs/oauth/services/base.py +++ b/readthedocs/oauth/services/base.py @@ -19,7 +19,7 @@ from requests_oauthlib import OAuth2Session log = logging.getLogger(__name__) -class Service(object): +class Service: """ Service mapping for local accounts. diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py index 0ce2f55a2..2586de569 100644 --- a/readthedocs/oauth/services/bitbucket.py +++ b/readthedocs/oauth/services/bitbucket.py @@ -119,8 +119,8 @@ class BitbucketService(Service): repo.private = fields['is_private'] # Default to HTTPS, use SSH for private repositories - clone_urls = dict((u['name'], u['href']) - for u in fields['links']['clone']) + clone_urls = {u['name']: u['href'] + for u in fields['links']['clone']} repo.clone_url = self.https_url_pattern.sub( 'https://bitbucket.org/', clone_urls.get('https') diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py index eae6e88dc..89b2c1dc7 100644 --- a/readthedocs/payments/forms.py +++ b/readthedocs/payments/forms.py @@ -15,7 +15,7 @@ from .utils import stripe log = logging.getLogger(__name__) -class StripeResourceMixin(object): +class StripeResourceMixin: """Stripe actions for resources, available as a Form mixin class.""" @@ -117,7 +117,7 @@ class StripeModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.customer = kwargs.pop('customer', None) - super(StripeModelForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def validate_stripe(self): """ @@ -147,7 +147,7 @@ class StripeModelForm(forms.ModelForm): raise any issues as validation errors. This is required because part of Stripe's validation happens on the API call to establish a subscription. """ - cleaned_data = super(StripeModelForm, self).clean() + cleaned_data = super().clean() # Form isn't valid, no need to try to associate a card now if not self.is_valid(): diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py index 0219da080..d90fc1699 100644 --- a/readthedocs/payments/mixins.py +++ b/readthedocs/payments/mixins.py @@ -5,12 +5,12 @@ from builtins import object from django.conf import settings -class StripeMixin(object): +class StripeMixin: """Adds Stripe publishable key to the context data.""" def get_context_data(self, **kwargs): - context = super(StripeMixin, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['stripe_publishable'] = settings.STRIPE_PUBLISHABLE return context diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index d55954fdb..48109f7ea 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -156,12 +156,12 @@ class ProjectAdmin(GuardedModelAdmin): total += count else: messages.add_message(request, messages.ERROR, - 'Project has multiple owners: {0}'.format(project)) + 'Project has multiple owners: {}'.format(project)) if total == 0: messages.add_message(request, messages.ERROR, 'No users banned') else: messages.add_message(request, messages.INFO, - 'Banned {0} user(s)'.format(total)) + 'Banned {} user(s)'.format(total)) ban_owner.short_description = 'Ban project owner' @@ -178,7 +178,7 @@ class ProjectAdmin(GuardedModelAdmin): return delete_selected(self, request, queryset) def get_actions(self, request): - actions = super(ProjectAdmin, self).get_actions(request) + actions = super().get_actions(request) actions['delete_selected'] = ( self.__class__.delete_selected_and_artifacts, 'delete_selected', diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 5f7f407f1..c447ab40c 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -52,17 +52,17 @@ class ProjectForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) - super(ProjectForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, commit=True): - project = super(ProjectForm, self).save(commit) + project = super().save(commit) if commit: if self.user and not project.users.filter(pk=self.user.pk).exists(): project.users.add(self.user) return project -class ProjectTriggerBuildMixin(object): +class ProjectTriggerBuildMixin: """ Mixin to trigger build on form save. @@ -73,7 +73,7 @@ class ProjectTriggerBuildMixin(object): def save(self, commit=True): """Trigger build on commit save.""" - project = super(ProjectTriggerBuildMixin, self).save(commit) + project = super().save(commit) if commit: trigger_build(project=project) return project @@ -90,7 +90,7 @@ class ProjectBasicsForm(ProjectForm): """Form for basic project fields.""" - class Meta(object): + class Meta: model = Project fields = ('name', 'repo', 'repo_type') @@ -101,7 +101,7 @@ class ProjectBasicsForm(ProjectForm): def __init__(self, *args, **kwargs): show_advanced = kwargs.pop('show_advanced', False) - super(ProjectBasicsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if show_advanced: self.fields['advanced'] = forms.BooleanField( required=False, @@ -112,7 +112,7 @@ class ProjectBasicsForm(ProjectForm): def save(self, commit=True): """Add remote repository relationship to the project instance.""" - instance = super(ProjectBasicsForm, self).save(commit) + instance = super().save(commit) remote_repo = self.cleaned_data.get('remote_repository', None) if remote_repo: if commit: @@ -165,7 +165,7 @@ class ProjectExtraForm(ProjectForm): """Additional project information form.""" - class Meta(object): + class Meta: model = Project fields = ( 'description', @@ -203,7 +203,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): 'environment.'), ) - class Meta(object): + class Meta: model = Project fields = ( # Standard build edits @@ -227,7 +227,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): ) def __init__(self, *args, **kwargs): - super(ProjectAdvancedForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) default_choice = (None, '-' * 9) all_versions = self.instance.versions.values_list( @@ -255,7 +255,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, ProjectExtraForm): - class Meta(object): + class Meta: model = Project fields = ( # Basics @@ -308,14 +308,14 @@ class ProjectRelationshipBaseForm(forms.ModelForm): parent = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = ProjectRelationship fields = '__all__' def __init__(self, *args, **kwargs): self.project = kwargs.pop('project') self.user = kwargs.pop('user') - super(ProjectRelationshipBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Don't display the update form with an editable child, as it will be # filtered out from the queryset anyways. if hasattr(self, 'instance') and self.instance.pk is not None: @@ -362,11 +362,11 @@ class DualCheckboxWidget(forms.CheckboxInput): """Checkbox with link to the version's built documentation.""" def __init__(self, version, attrs=None, check_test=bool): - super(DualCheckboxWidget, self).__init__(attrs, check_test) + super().__init__(attrs, check_test) self.version = version def render(self, name, value, attrs=None, renderer=None): - checkbox = super(DualCheckboxWidget, self).render(name, value, attrs, renderer) + checkbox = super().render(name, value, attrs, renderer) icon = self.render_icon() return mark_safe('{}{}'.format(checkbox, icon)) @@ -459,7 +459,7 @@ class BaseUploadHTMLForm(forms.Form): def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) - super(BaseUploadHTMLForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean(self): version_slug = self.cleaned_data['version'] @@ -499,7 +499,7 @@ class UserForm(forms.Form): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(UserForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_user(self): name = self.cleaned_data['user'] @@ -525,7 +525,7 @@ class EmailHookForm(forms.Form): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(EmailHookForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_email(self): self.email = EmailHook.objects.get_or_create( @@ -543,7 +543,7 @@ class WebHookForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(WebHookForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, commit=True): self.webhook = WebHook.objects.get_or_create( @@ -565,7 +565,7 @@ class TranslationBaseForm(forms.Form): def __init__(self, *args, **kwargs): self.parent = kwargs.pop('parent', None) self.user = kwargs.pop('user') - super(TranslationBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['project'].choices = self.get_choices() def get_choices(self): @@ -646,13 +646,13 @@ class RedirectForm(forms.ModelForm): """Form for project redirects.""" - class Meta(object): + class Meta: model = Redirect fields = ['redirect_type', 'from_url', 'to_url'] def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(RedirectForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, **_): # pylint: disable=arguments-differ # TODO this should respect the unused argument `commit`. It's not clear @@ -673,13 +673,13 @@ class DomainBaseForm(forms.ModelForm): project = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = Domain exclude = ['machine', 'cname', 'count'] # pylint: disable=modelform-uses-exclude def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(DomainBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_project(self): return self.project @@ -717,13 +717,13 @@ class IntegrationForm(forms.ModelForm): project = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = Integration exclude = ['provider_data', 'exchanges'] # pylint: disable=modelform-uses-exclude def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(IntegrationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Alter the integration type choices to only provider webhooks self.fields['integration_type'].choices = Integration.WEBHOOK_INTEGRATIONS # yapf: disable # noqa @@ -732,20 +732,20 @@ class IntegrationForm(forms.ModelForm): def save(self, commit=True): self.instance = Integration.objects.subclass(self.instance) - return super(IntegrationForm, self).save(commit) + return super().save(commit) class ProjectAdvertisingForm(forms.ModelForm): """Project promotion opt-out form.""" - class Meta(object): + class Meta: model = Project fields = ['allow_promos'] def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(ProjectAdvertisingForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class FeatureForm(forms.ModelForm): @@ -760,10 +760,10 @@ class FeatureForm(forms.ModelForm): feature_id = forms.ChoiceField() - class Meta(object): + class Meta: model = Feature fields = ['projects', 'feature_id', 'default_true'] def __init__(self, *args, **kwargs): - super(FeatureForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['feature_id'].choices = Feature.FEATURES diff --git a/readthedocs/projects/migrations/0007_migrate_canonical_data.py b/readthedocs/projects/migrations/0007_migrate_canonical_data.py index 743d6a145..8c842a6a0 100644 --- a/readthedocs/projects/migrations/0007_migrate_canonical_data.py +++ b/readthedocs/projects/migrations/0007_migrate_canonical_data.py @@ -16,10 +16,10 @@ def migrate_canonical(apps, schema_editor): url=project.canonical_url, canonical=True, ) - print(u"Added {url} to {project}".format(url=project.canonical_url, project=project.name)) + print("Added {url} to {project}".format(url=project.canonical_url, project=project.name)) except Exception as e: print(e) - print(u"Failed adding {url} to {project}".format( + print("Failed adding {url} to {project}".format( url=project.canonical_url, project=project.name )) diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py index ef60b2a00..636f200bf 100644 --- a/readthedocs/projects/migrations/0010_migrate_domain_data.py +++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py @@ -24,10 +24,10 @@ def migrate_url(apps, schema_editor): try: domain.domain = domain_string domain.save() - print(u"Added {domain} from {url}".format(url=domain.url, domain=domain_string)) + print("Added {domain} from {url}".format(url=domain.url, domain=domain_string)) except Exception as e: print(e) - print(u"Failed {domain} from {url}".format(url=domain.url, domain=domain_string)) + print("Failed {domain} from {url}".format(url=domain.url, domain=domain_string)) dms = Domain.objects.filter(domain=domain_string).order_by('-count') if dms.count() > 1: diff --git a/readthedocs/projects/migrations/0016_build-queue-name.py b/readthedocs/projects/migrations/0016_build-queue-name.py index 46833ada7..d4e2201b2 100644 --- a/readthedocs/projects/migrations/0016_build-queue-name.py +++ b/readthedocs/projects/migrations/0016_build-queue-name.py @@ -11,7 +11,7 @@ def update_build_queue(apps, schema): for project in Project.objects.all(): if project.build_queue is not None: if not project.build_queue.startswith('build-'): - project.build_queue = 'build-{0}'.format(project.build_queue) + project.build_queue = 'build-{}'.format(project.build_queue) project.save() diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index d02ecfc76..c87295b86 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -56,12 +56,12 @@ class ProjectRelationship(models.Model): objects = ChildRelatedProjectQuerySet.as_manager() def __str__(self): - return '%s -> %s' % (self.parent, self.child) + return '{} -> {}'.format(self.parent, self.child) def save(self, *args, **kwargs): # pylint: disable=arguments-differ if not self.alias: self.alias = self.child.slug - super(ProjectRelationship, self).save(*args, **kwargs) + super().save(*args, **kwargs) # HACK def get_absolute_url(self): @@ -251,7 +251,7 @@ class Project(models.Model): objects = ProjectQuerySet.as_manager() all_objects = models.Manager() - class Meta(object): + class Meta: ordering = ('slug',) permissions = ( # Translators: Permission around whether a user can view the @@ -275,7 +275,7 @@ class Project(models.Model): # documentation type to Sphinx for rST and Mkdocs for markdown. # It now just forces Sphinx, due to markdown support. self.documentation_type = 'sphinx' - super(Project, self).save(*args, **kwargs) + super().save(*args, **kwargs) for owner in self.users.all(): assign('view_project', owner, self) try: @@ -379,7 +379,7 @@ class Project(models.Model): settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug) if include_file: path = os.path.join( - path, '%s.%s' % (self.slug, type_.replace('htmlzip', 'zip'))) + path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip'))) return path def get_production_media_url(self, type_, version_slug, full_path=True): @@ -393,7 +393,7 @@ class Project(models.Model): except NoReverseMatch: return '' if full_path: - path = '//%s%s' % (settings.PRODUCTION_DOMAIN, path) + path = '//{}{}'.format(settings.PRODUCTION_DOMAIN, path) return path def subdomain(self): @@ -856,7 +856,7 @@ class APIProject(Project): del kwargs[key] except KeyError: pass - super(APIProject, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Overwrite the database property with the value from the API self.ad_free = ad_free @@ -903,7 +903,7 @@ class ImportedFile(models.Model): return resolve(project=self.project, version_slug=self.version.slug, filename=self.path) def __str__(self): - return '%s: %s' % (self.name, self.project) + return '{}: {}'.format(self.name, self.project) class Notification(models.Model): @@ -911,7 +911,7 @@ class Notification(models.Model): related_name='%(class)s_notifications') objects = RelatedProjectQuerySet.as_manager() - class Meta(object): + class Meta: abstract = True @@ -962,7 +962,7 @@ class Domain(models.Model): objects = RelatedProjectQuerySet.as_manager() - class Meta(object): + class Meta: ordering = ('-canonical', '-machine', 'domain') def __str__(self): @@ -975,7 +975,7 @@ class Domain(models.Model): self.domain = parsed.netloc else: self.domain = parsed.path - super(Domain, self).save(*args, **kwargs) + super().save(*args, **kwargs) broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk],) @@ -983,7 +983,7 @@ class Domain(models.Model): from readthedocs.projects import tasks broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk, True],) - super(Domain, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) @python_2_unicode_compatible @@ -1053,7 +1053,7 @@ class Feature(models.Model): objects = FeatureQuerySet.as_manager() def __str__(self): - return '{0} feature'.format( + return '{} feature'.format( self.get_feature_display(), ) diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index bcc819137..10b6660de 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -85,7 +85,7 @@ from .signals import ( log = logging.getLogger(__name__) -class SyncRepositoryMixin(object): +class SyncRepositoryMixin: """Mixin that handles the VCS sync/update.""" @@ -1217,11 +1217,11 @@ def email_notification(version, build, email): 'pk': build.pk, 'error': build.error, }, - 'build_url': 'https://{0}{1}'.format( + 'build_url': 'https://{}{}'.format( getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'), build.get_absolute_url(), ), - 'unsub_url': 'https://{0}{1}'.format( + 'unsub_url': 'https://{}{}'.format( getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'), reverse('projects_notifications', args=[version.project.slug]), ), @@ -1320,7 +1320,7 @@ def update_static_metadata(project_pk, path=None): LOG_TEMPLATE.format( project=project.slug, version='', - msg='Cannot write to metadata.json: {0}'.format(e), + msg='Cannot write to metadata.json: {}'.format(e), ) ) @@ -1396,7 +1396,7 @@ def finish_inactive_builds(): build.error = _( 'This build was terminated due to inactivity. If you ' 'continue to encounter this error, file a support ' - 'request with and reference this build id ({0}).'.format(build.pk), + 'request with and reference this build id ({}).'.format(build.pk), ) build.save() builds_finished += 1 diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py index 01d350a43..f2b7b4687 100644 --- a/readthedocs/projects/validators.py +++ b/readthedocs/projects/validators.py @@ -28,13 +28,13 @@ class DomainNameValidator(RegexValidator): def __init__(self, accept_idna=True, **kwargs): message = kwargs.get('message') self.accept_idna = accept_idna - super(DomainNameValidator, self).__init__(**kwargs) + super().__init__(**kwargs) if not self.accept_idna and message is None: self.message = _('Enter a valid domain name value') def __call__(self, value): try: - super(DomainNameValidator, self).__call__(value) + super().__call__(value) except ValidationError as exc: if not self.accept_idna: raise @@ -44,14 +44,14 @@ class DomainNameValidator(RegexValidator): idnavalue = value.encode('idna') except UnicodeError: raise exc - super(DomainNameValidator, self).__call__(idnavalue) + super().__call__(idnavalue) validate_domain_name = DomainNameValidator() @deconstructible -class RepositoryURLValidator(object): +class RepositoryURLValidator: disallow_relative_url = True diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 2a7cffa5b..d3ef3d3a1 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -25,7 +25,7 @@ def parse_version_failsafe(version_string): :rtype: packaging.version.Version """ - if not isinstance(version_string, six.text_type): + if not isinstance(version_string, str): uni_version = version_string.decode('utf-8') else: uni_version = version_string diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py index 2bdb17fdf..b6e8e387d 100644 --- a/readthedocs/projects/views/base.py +++ b/readthedocs/projects/views/base.py @@ -21,13 +21,13 @@ log = logging.getLogger(__name__) USER_MATURITY_DAYS = getattr(settings, 'USER_MATURITY_DAYS', 7) -class ProjectOnboardMixin(object): +class ProjectOnboardMixin: """Add project onboard context data to project object views.""" def get_context_data(self, **kwargs): """Add onboard context data.""" - context = super(ProjectOnboardMixin, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) # If more than 1 project, don't show onboarding at all. This could # change in the future, to onboard each user maybe? if Project.objects.for_admin_user(self.request.user).count() > 1: @@ -51,7 +51,7 @@ class ProjectOnboardMixin(object): # Mixins -class ProjectAdminMixin(object): +class ProjectAdminMixin: """ Mixin class that provides project sublevel objects. @@ -78,7 +78,7 @@ class ProjectAdminMixin(object): def get_context_data(self, **kwargs): """Add project to context data.""" - context = super(ProjectAdminMixin, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['project'] = self.get_project() return context @@ -88,7 +88,7 @@ class ProjectAdminMixin(object): return self.form_class(data, files, **kwargs) -class ProjectSpamMixin(object): +class ProjectSpamMixin: """Protects POST views from spammers.""" @@ -100,7 +100,7 @@ class ProjectSpamMixin(object): ) return HttpResponseRedirect(self.get_failure_url()) try: - return super(ProjectSpamMixin, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) except ProjectSpamError: date_maturity = timezone.now() - timedelta(days=USER_MATURITY_DAYS) if request.user.date_joined > date_maturity: diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py index 50e03beb4..1692b0e46 100644 --- a/readthedocs/projects/views/mixins.py +++ b/readthedocs/projects/views/mixins.py @@ -7,7 +7,7 @@ from django.shortcuts import get_object_or_404 from readthedocs.projects.models import Project -class ProjectRelationMixin(object): +class ProjectRelationMixin: """ Mixin class for constructing model views for project dashboard. @@ -41,6 +41,6 @@ class ProjectRelationMixin(object): ) def get_context_data(self, **kwargs): - context = super(ProjectRelationMixin, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context[self.project_context_object_name] = self.get_project() return context diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 330c22b5b..22b355f94 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -84,7 +84,7 @@ class ProjectDashboard(PrivateViewMixin, ListView): return Project.objects.dashboard(self.request.user) def get_context_data(self, **kwargs): - context = super(ProjectDashboard, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) return context @@ -250,7 +250,7 @@ class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView): def get_template_names(self): """Return template names based on step name.""" - return 'projects/import_{0}.html'.format(self.steps.current) + return 'projects/import_{}.html'.format(self.steps.current) def done(self, form_list, **kwargs): """ @@ -351,7 +351,7 @@ class ImportDemoView(PrivateViewMixin, View): def get_form_data(self): """Get form data to post to import form.""" return { - 'name': '{0}-demo'.format(self.request.user.username), + 'name': '{}-demo'.format(self.request.user.username), 'repo_type': 'git', 'repo': 'https://github.com/readthedocs/template.git', } @@ -405,7 +405,7 @@ class ImportView(PrivateViewMixin, TemplateView): ) )), # yapf: disable ) - return super(ImportView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): initial_data = {} @@ -419,7 +419,7 @@ class ImportView(PrivateViewMixin, TemplateView): return self.wizard_class.as_view(initial_dict=initial_data)(request) def get_context_data(self, **kwargs): - context = super(ImportView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['view_csrf_token'] = get_token(self.request) context['has_connected_accounts'] = SocialAccount.objects.filter( user=self.request.user, @@ -440,10 +440,7 @@ class ProjectRelationshipMixin(ProjectAdminMixin, PrivateViewMixin): def get_form(self, data=None, files=None, **kwargs): kwargs['user'] = self.request.user - return super( - ProjectRelationshipMixin, - self, - ).get_form(data, files, **kwargs) + return super().get_form(data, files, **kwargs) def form_valid(self, form): broadcast( @@ -451,7 +448,7 @@ class ProjectRelationshipMixin(ProjectAdminMixin, PrivateViewMixin): task=tasks.symlink_subproject, args=[self.get_project().pk], ) - return super(ProjectRelationshipMixin, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse('projects_subprojects', args=[self.get_project().slug]) @@ -460,7 +457,7 @@ class ProjectRelationshipMixin(ProjectAdminMixin, PrivateViewMixin): class ProjectRelationshipList(ProjectRelationshipMixin, ListView): def get_context_data(self, **kwargs): - ctx = super(ProjectRelationshipList, self).get_context_data(**kwargs) + ctx = super().get_context_data(**kwargs) ctx['superproject'] = self.project.superprojects.first() return ctx @@ -775,7 +772,7 @@ class IntegrationMixin(ProjectAdminMixin, PrivateViewMixin): def get_template_names(self): if self.template_name: return self.template_name - return 'projects/integration{0}.html'.format(self.template_name_suffix) + return 'projects/integration{}.html'.format(self.template_name_suffix) class IntegrationList(IntegrationMixin, ListView): @@ -810,7 +807,7 @@ class IntegrationDetail(IntegrationMixin, DetailView): integration_type = self.get_integration().integration_type suffix = self.SUFFIX_MAP.get(integration_type, integration_type) return ( - 'projects/integration_{0}{1}.html' + 'projects/integration_{}{}.html' .format(suffix, self.template_name_suffix) ) diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py index 90f5ef978..028571a29 100644 --- a/readthedocs/projects/views/public.py +++ b/readthedocs/projects/views/public.py @@ -62,7 +62,7 @@ class ProjectIndex(ListView): return queryset def get_context_data(self, **kwargs): - context = super(ProjectIndex, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['person'] = self.user context['tag'] = self.tag return context @@ -82,7 +82,7 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView): return Project.objects.protected(self.request.user) def get_context_data(self, **kwargs): - context = super(ProjectDetailView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) project = self.get_object() context['versions'] = Version.objects.public( @@ -94,7 +94,7 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView): version_slug = project.get_default_version() - context['badge_url'] = '%s://%s%s?version=%s' % ( + context['badge_url'] = '{}://{}{}?version={}'.format( protocol, settings.PRODUCTION_DOMAIN, reverse('project_badge', args=[project.slug]), @@ -192,7 +192,7 @@ def project_download_media(request, project_slug, type_, version_slug): if privacy_level == 'public' or settings.DEBUG: path = os.path.join( settings.MEDIA_URL, type_, project_slug, version_slug, - '%s.%s' % (project_slug, type_.replace('htmlzip', 'zip'))) + '{}.{}'.format(project_slug, type_.replace('htmlzip', 'zip'))) return HttpResponseRedirect(path) # Get relative media path @@ -207,7 +207,7 @@ def project_download_media(request, project_slug, type_, version_slug): response['Content-Encoding'] = encoding response['X-Accel-Redirect'] = path # Include version in filename; this fixes a long-standing bug - filename = '%s-%s.%s' % ( + filename = '{}-{}.{}'.format( project_slug, version_slug, path.split('.')[-1]) response['Content-Disposition'] = 'filename=%s' % filename return response diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py index cbd080ca2..f8eddb16c 100644 --- a/readthedocs/redirects/models.py +++ b/readthedocs/redirects/models.py @@ -76,7 +76,7 @@ class Redirect(models.Model): objects = RedirectManager() - class Meta(object): + class Meta: verbose_name = _('redirect') verbose_name_plural = _('redirects') ordering = ('-update_dt',) diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py index 615872d30..a9d7aa8d9 100644 --- a/readthedocs/restapi/permissions.py +++ b/readthedocs/restapi/permissions.py @@ -51,11 +51,11 @@ class APIPermission(permissions.IsAuthenticatedOrReadOnly): """ def has_permission(self, request, view): - has_perm = super(APIPermission, self).has_permission(request, view) + has_perm = super().has_permission(request, view) return has_perm or (request.user and request.user.is_staff) def has_object_permission(self, request, view, obj): - has_perm = super(APIPermission, self).has_object_permission( + has_perm = super().has_object_permission( request, view, obj) return has_perm or (request.user and request.user.is_staff) diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py index 1d473a9ea..ba6a88ddc 100644 --- a/readthedocs/restapi/serializers.py +++ b/readthedocs/restapi/serializers.py @@ -15,7 +15,7 @@ from readthedocs.projects.models import Project, Domain class ProjectSerializer(serializers.ModelSerializer): canonical_url = serializers.ReadOnlyField(source='get_docs_url') - class Meta(object): + class Meta: model = Project fields = ( 'id', @@ -70,7 +70,7 @@ class VersionSerializer(serializers.ModelSerializer): project = ProjectSerializer() downloads = serializers.DictField(source='get_downloads', read_only=True) - class Meta(object): + class Meta: model = Version fields = ( 'id', @@ -93,7 +93,7 @@ class BuildCommandSerializer(serializers.ModelSerializer): run_time = serializers.ReadOnlyField() - class Meta(object): + class Meta: model = BuildCommandResult exclude = ('') @@ -111,7 +111,7 @@ class BuildSerializer(serializers.ModelSerializer): # https://github.com/dmkoch/django-jsonfield/issues/188#issuecomment-300439829 config = serializers.JSONField(required=False) - class Meta(object): + class Meta: model = Build # `_config` should be excluded to avoid conflicts with `config` exclude = ('builder', '_config') @@ -136,7 +136,7 @@ class SearchIndexSerializer(serializers.Serializer): class DomainSerializer(serializers.ModelSerializer): project = ProjectSerializer() - class Meta(object): + class Meta: model = Domain fields = ( 'id', @@ -150,7 +150,7 @@ class DomainSerializer(serializers.ModelSerializer): class RemoteOrganizationSerializer(serializers.ModelSerializer): - class Meta(object): + class Meta: model = RemoteOrganization exclude = ('json', 'email', 'users') @@ -162,7 +162,7 @@ class RemoteRepositorySerializer(serializers.ModelSerializer): organization = RemoteOrganizationSerializer() matches = serializers.SerializerMethodField() - class Meta(object): + class Meta: model = RemoteRepository exclude = ('json', 'users') @@ -184,7 +184,7 @@ class SocialAccountSerializer(serializers.ModelSerializer): avatar_url = serializers.URLField(source='get_avatar_url') provider = ProviderSerializer(source='get_provider') - class Meta(object): + class Meta: model = SocialAccount exclude = ('extra_data',) diff --git a/readthedocs/restapi/views/footer_views.py b/readthedocs/restapi/views/footer_views.py index f09ceaff5..ad5ec7028 100644 --- a/readthedocs/restapi/views/footer_views.py +++ b/readthedocs/restapi/views/footer_views.py @@ -37,8 +37,8 @@ def get_version_compare_data(project, base_version=None): highest_version_obj, highest_version_comparable = highest_version( versions_qs) ret_val = { - 'project': six.text_type(highest_version_obj), - 'version': six.text_type(highest_version_comparable), + 'project': str(highest_version_obj), + 'version': str(highest_version_comparable), 'is_highest': True, } if highest_version_obj: diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py index 9a27e2ef9..495051840 100644 --- a/readthedocs/restapi/views/integrations.py +++ b/readthedocs/restapi/views/integrations.py @@ -43,7 +43,7 @@ BITBUCKET_EVENT_HEADER = 'HTTP_X_EVENT_KEY' BITBUCKET_PUSH = 'repo:push' -class WebhookMixin(object): +class WebhookMixin: """Base class for Webhook mixins.""" @@ -75,7 +75,7 @@ class WebhookMixin(object): def finalize_response(self, req, *args, **kwargs): """If the project was set on POST, store an HTTP exchange.""" - resp = super(WebhookMixin, self).finalize_response(req, *args, **kwargs) + resp = super().finalize_response(req, *args, **kwargs) if hasattr(self, 'project') and self.project: HttpExchange.objects.from_exchange( req, @@ -180,7 +180,7 @@ class GitHubWebhookView(WebhookMixin, APIView): return json.loads(self.request.data['payload']) except (ValueError, KeyError): pass - return super(GitHubWebhookView, self).get_data() + return super().get_data() def handle_webhook(self): # Get event and trigger other webhook events @@ -350,7 +350,7 @@ class IsAuthenticatedOrHasToken(permissions.IsAuthenticated): """ def has_permission(self, request, view): - has_perm = (super(IsAuthenticatedOrHasToken, self) + has_perm = (super() .has_permission(request, view)) return has_perm or 'token' in request.data @@ -404,7 +404,7 @@ class APIWebhookView(WebhookMixin, APIView): 'branches', [self.project.get_default_branch()] ) - if isinstance(branches, six.string_types): + if isinstance(branches, str): branches = [branches] return self.get_response_push(self.project, branches) except TypeError: diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py index f11ec5a49..c6813f22f 100644 --- a/readthedocs/rtd_tests/base.py +++ b/readthedocs/rtd_tests/base.py @@ -42,7 +42,7 @@ class MockBuildTestCase(TestCase): pass -class RequestFactoryTestMixin(object): +class RequestFactoryTestMixin: """ Adds helper methods for testing with :py:class:`RequestFactory` @@ -107,14 +107,14 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase): if not self.url: raise Exception('Missing wizard URL') try: - data = dict( - ('{0}-{1}'.format(step, k), v) + data = { + '{}-{}'.format(step, k): v for (k, v) in list(self.step_data[step].items()) - ) + } except KeyError: pass # Update with prefixed step data - data['{0}-current_step'.format(self.wizard_class_slug)] = step + data['{}-current_step'.format(self.wizard_class_slug)] = step view = self.wizard_class.as_view() req = self.request(self.url, method='post', data=data, **kwargs) resp = view(req) @@ -146,7 +146,7 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase): response.render() self.assertContains( response, - u'name="{0}-current_step"'.format(self.wizard_class_slug) + 'name="{}-current_step"'.format(self.wizard_class_slug) ) # We use camelCase on purpose here to conform with unittest's naming @@ -170,4 +170,4 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase): self.assertIn(field, response.context_data['wizard']['form'].errors) if match is not None: error = response.context_data['wizard']['form'].errors[field] - self.assertRegex(six.text_type(error), match) # noqa + self.assertRegex(str(error), match) # noqa diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 4007dcfab..11f872849 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -13,7 +13,7 @@ source_parsers = { '.md': CommonMarkParser, } master_doc = 'index' -project = u'Pip' +project = 'Pip' copyright = str(datetime.now().year) version = '0.8.1' release = '0.8.1' @@ -23,6 +23,6 @@ htmlhelp_basename = 'pip' html_theme = 'sphinx_rtd_theme' file_insertion_enabled = False latex_documents = [ - ('index', 'pip.tex', u'Pip Documentation', - u'', 'manual'), + ('index', 'pip.tex', 'Pip Documentation', + '', 'manual'), ] diff --git a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py index c6c9fcb64..68b327ca8 100644 --- a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py +++ b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py @@ -41,8 +41,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'sample' -copyright = u'2011, Dan' +project = 'sample' +copyright = '2011, Dan' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -180,8 +180,8 @@ htmlhelp_basename = 'sampledoc' # Grouping the document tree into LaTeX files. List of tuples (source start # file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'sample.tex', u'sample Documentation', - u'Dan', 'manual'), + ('index', 'sample.tex', 'sample Documentation', + 'Dan', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,6 +213,6 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'sample', u'sample Documentation', - [u'Dan'], 1) + ('index', 'sample', 'sample Documentation', + ['Dan'], 1) ] diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py index 4b963b769..3b2a28f49 100644 --- a/readthedocs/rtd_tests/mocks/environment.py +++ b/readthedocs/rtd_tests/mocks/environment.py @@ -4,7 +4,7 @@ from builtins import object import mock -class EnvironmentMockGroup(object): +class EnvironmentMockGroup: """Mock out necessary environment pieces""" diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py index 84c40d7c4..68d1e5443 100644 --- a/readthedocs/rtd_tests/mocks/mock_api.py +++ b/readthedocs/rtd_tests/mocks/mock_api.py @@ -8,7 +8,7 @@ import mock # Mock tastypi API. -class ProjectData(object): +class ProjectData: def get(self): return dict() @@ -18,7 +18,7 @@ class ProjectData(object): def mock_version(repo): """Construct and return a class implementing the Version interface.""" - class MockVersion(object): + class MockVersion: def __init__(self, x=None): pass @@ -71,7 +71,7 @@ def mock_version(repo): return MockVersion -class MockApi(object): +class MockApi: def __init__(self, repo): self.version = mock_version(repo) diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index 707a4da79..af63a2378 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -359,7 +359,7 @@ class APIBuildTests(TestCase): client.force_authenticate(user=api_user) build = get(Build, project_id=1, version_id=1, state='cloning') resp = client.put( - '/api/v2/build/{0}/'.format(build.pk), + '/api/v2/build/{}/'.format(build.pk), { 'project': 1, 'version': 1, @@ -381,11 +381,11 @@ class APIBuildTests(TestCase): api_user = get(User, staff=False, password='test') client.force_authenticate(user=api_user) - resp = client.get('/api/v2/build/{0}/'.format(build.pk), format='json') + resp = client.get('/api/v2/build/{}/'.format(build.pk), format='json') self.assertEqual(resp.status_code, 200) client.force_authenticate(user=User.objects.get(username='super')) - resp = client.get('/api/v2/build/{0}/'.format(build.pk), format='json') + resp = client.get('/api/v2/build/{}/'.format(build.pk), format='json') self.assertEqual(resp.status_code, 200) self.assertIn('builder', resp.data) @@ -443,7 +443,7 @@ class APIBuildTests(TestCase): api_user = get(User, user='test', password='test') client.force_authenticate(user=api_user) - resp = client.get('/api/v2/build/{0}.txt'.format(build.pk)) + resp = client.get('/api/v2/build/{}.txt'.format(build.pk)) self.assertEqual(resp.status_code, 200) self.assertIn('Read the Docs build information', resp.content.decode()) @@ -487,7 +487,7 @@ class APIBuildTests(TestCase): api_user = get(User, user='test', password='test') client.force_authenticate(user=api_user) - resp = client.get('/api/v2/build/{0}.txt'.format(build.pk)) + resp = client.get('/api/v2/build/{}.txt'.format(build.pk)) self.assertEqual(resp.status_code, 200) self.assertIn('Read the Docs build information', resp.content.decode()) @@ -530,7 +530,7 @@ class APIBuildTests(TestCase): api_user = get(User, user='test', password='test') client.force_authenticate(user=api_user) - resp = client.get('/api/v2/build/{0}.txt'.format(build.pk)) + resp = client.get('/api/v2/build/{}.txt'.format(build.pk)) self.assertEqual(resp.status_code, 200) self.assertIn('Read the Docs build information', resp.content.decode()) @@ -556,7 +556,7 @@ class APIBuildTests(TestCase): api_user = get(User, user='test', password='test') client.force_authenticate(user=api_user) - resp = client.get('/api/v2/build/{0}.txt'.format(404)) + resp = client.get('/api/v2/build/{}.txt'.format(404)) self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) def test_build_filter_by_commit(self): @@ -661,8 +661,7 @@ class APITests(TestCase): resp = client.get('/api/v2/project/%s/' % (project.pk)) self.assertEqual(resp.status_code, 200) self.assertIn('features', resp.data) - six.assertCountEqual( - self, + self.assertCountEqual( resp.data['features'], [feature1.feature_id, feature2.feature_id], ) @@ -868,7 +867,7 @@ class IntegrationsTests(TestCase): self.project.save() response = client.post( - '/api/v2/webhook/github/{0}/'.format( + '/api/v2/webhook/github/{}/'.format( self.project.slug, ), self.github_payload, @@ -883,7 +882,7 @@ class IntegrationsTests(TestCase): client = APIClient() client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'master'}, format='json', ) @@ -891,7 +890,7 @@ class IntegrationsTests(TestCase): [mock.call(force=True, version=self.version, project=self.project)]) client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'non-existent'}, format='json', ) @@ -899,7 +898,7 @@ class IntegrationsTests(TestCase): [mock.call(force=True, version=mock.ANY, project=self.project)]) client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'refs/heads/master'}, format='json', ) @@ -911,7 +910,7 @@ class IntegrationsTests(TestCase): client = APIClient() client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'v1.0'}, format='json', ) @@ -919,7 +918,7 @@ class IntegrationsTests(TestCase): [mock.call(force=True, version=self.version_tag, project=self.project)]) client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'refs/heads/non-existent'}, format='json', ) @@ -927,7 +926,7 @@ class IntegrationsTests(TestCase): [mock.call(force=True, version=mock.ANY, project=self.project)]) client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'ref': 'refs/tags/v1.0'}, format='json', ) @@ -986,7 +985,7 @@ class IntegrationsTests(TestCase): """GitHub webhook unhandled event.""" client = APIClient() resp = client.post( - '/api/v2/webhook/github/{0}/'.format(self.project.slug), + '/api/v2/webhook/github/{}/'.format(self.project.slug), {'foo': 'bar'}, format='json', HTTP_X_GITHUB_EVENT='pull_request', @@ -1146,7 +1145,7 @@ class IntegrationsTests(TestCase): """GitLab webhook unhandled event.""" client = APIClient() resp = client.post( - '/api/v2/webhook/gitlab/{0}/'.format(self.project.slug), + '/api/v2/webhook/gitlab/{}/'.format(self.project.slug), {'object_kind': 'pull_request'}, format='json', ) @@ -1236,7 +1235,7 @@ class IntegrationsTests(TestCase): """Bitbucket webhook unhandled event.""" client = APIClient() resp = client.post( - '/api/v2/webhook/bitbucket/{0}/'.format(self.project.slug), + '/api/v2/webhook/bitbucket/{}/'.format(self.project.slug), {'foo': 'bar'}, format='json', HTTP_X_EVENT_KEY='pull_request') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data['detail'], 'Unhandled webhook event') @@ -1244,7 +1243,7 @@ class IntegrationsTests(TestCase): def test_generic_api_fails_without_auth(self, trigger_build): client = APIClient() resp = client.post( - '/api/v2/webhook/generic/{0}/'.format(self.project.slug), + '/api/v2/webhook/generic/{}/'.format(self.project.slug), {}, format='json', ) @@ -1262,7 +1261,7 @@ class IntegrationsTests(TestCase): ) self.assertIsNotNone(integration.token) resp = client.post( - '/api/v2/webhook/{0}/{1}/'.format( + '/api/v2/webhook/{}/{}/'.format( self.project.slug, integration.pk, ), @@ -1273,7 +1272,7 @@ class IntegrationsTests(TestCase): self.assertTrue(resp.data['build_triggered']) # Test nonexistent branch resp = client.post( - '/api/v2/webhook/{0}/{1}/'.format( + '/api/v2/webhook/{}/{}/'.format( self.project.slug, integration.pk, ), @@ -1289,7 +1288,7 @@ class IntegrationsTests(TestCase): self.project.users.add(user) client.force_authenticate(user=user) resp = client.post( - '/api/v2/webhook/generic/{0}/'.format(self.project.slug), + '/api/v2/webhook/generic/{}/'.format(self.project.slug), {}, format='json', ) @@ -1304,7 +1303,7 @@ class IntegrationsTests(TestCase): project=self.project, integration_type=Integration.API_WEBHOOK) self.assertIsNotNone(integration.token) resp = client.post( - '/api/v2/webhook/{0}/{1}/'.format( + '/api/v2/webhook/{}/{}/'.format( self.project.slug, integration.pk, ), diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index ca0de802c..34510778f 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -34,7 +34,7 @@ from readthedocs.rtd_tests.utils import ( class TestGitBackend(RTDTestCase): def setUp(self): git_repo = make_test_git() - super(TestGitBackend, self).setUp() + super().setUp() self.eric = User(username='eric') self.eric.set_password('test') self.eric.save() @@ -128,7 +128,7 @@ class TestGitBackend(RTDTestCase): # so we need to hack the repo path repo.working_dir = repo_path self.assertEqual( - set(['v01', 'v02', 'release-ünîø∂é']), + {'v01', 'v02', 'release-ünîø∂é'}, {vcs.verbose_name for vcs in repo.tags}, ) @@ -207,28 +207,28 @@ class TestGitBackend(RTDTestCase): # We still have all branches and tags in the local repo self.assertEqual( - set(['v01', 'v02']), - set(vcs.verbose_name for vcs in repo.tags) + {'v01', 'v02'}, + {vcs.verbose_name for vcs in repo.tags} ) self.assertEqual( - set([ + { 'invalidsubmodule', 'master', 'submodule', 'newbranch', - ]), - set(vcs.verbose_name for vcs in repo.branches) + }, + {vcs.verbose_name for vcs in repo.branches} ) repo.update() # We don't have the eliminated branches and tags in the local repo self.assertEqual( - set(['v01']), - set(vcs.verbose_name for vcs in repo.tags) + {'v01'}, + {vcs.verbose_name for vcs in repo.tags} ) self.assertEqual( - set([ + { 'invalidsubmodule', 'master', 'submodule' - ]), - set(vcs.verbose_name for vcs in repo.branches) + }, + {vcs.verbose_name for vcs in repo.branches} ) @@ -236,7 +236,7 @@ class TestHgBackend(RTDTestCase): def setUp(self): hg_repo = make_test_hg() - super(TestHgBackend, self).setUp() + super().setUp() self.eric = User(username='eric') self.eric.set_password('test') self.eric.save() diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py index b0c2186d4..9085d6f79 100644 --- a/readthedocs/rtd_tests/tests/test_build_config.py +++ b/readthedocs/rtd_tests/tests/test_build_config.py @@ -28,7 +28,7 @@ class PathValidator(Validator): configuration_file = '.' def _is_valid(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): file_ = path.join( path.dirname(self.configuration_file), value diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index 11a9e31e4..e718e220b 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -30,7 +30,7 @@ class TestCeleryBuilding(RTDTestCase): def setUp(self): repo = make_test_git() self.repo = repo - super(TestCeleryBuilding, self).setUp() + super().setUp() self.eric = User(username='eric') self.eric.set_password('test') self.eric.save() @@ -44,7 +44,7 @@ class TestCeleryBuilding(RTDTestCase): def tearDown(self): shutil.rmtree(self.repo) - super(TestCeleryBuilding, self).tearDown() + super().tearDown() def test_remove_dir(self): directory = mkdtemp() diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 2b2c6ceb4..8474c4bea 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -288,7 +288,7 @@ class LoadConfigTests(TestCase): @pytest.mark.django_db @mock.patch('readthedocs.projects.models.Project.checkout_path') -class TestLoadConfigV2(object): +class TestLoadConfigV2: @pytest.fixture(autouse=True) def create_project(self): diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 6a954de74..1b8f11c38 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -45,7 +45,7 @@ from readthedocs.rtd_tests.tests.test_config_integration import create_load DUMMY_BUILD_ID = 123 -SAMPLE_UNICODE = u'HérÉ îß sömê ünïçó∂é' +SAMPLE_UNICODE = 'HérÉ îß sömê ünïçó∂é' SAMPLE_UTF8_BYTES = SAMPLE_UNICODE.encode('utf-8') @@ -83,7 +83,7 @@ class TestLocalBuildEnvironment(TestCase): self.assertTrue(build_env.done) self.assertTrue(build_env.successful) self.assertEqual(len(build_env.commands), 1) - self.assertEqual(build_env.commands[0].output, u'This is okay') + self.assertEqual(build_env.commands[0].output, 'This is okay') # api() is not called anymore, we use api_v2 instead self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called) @@ -103,7 +103,7 @@ class TestLocalBuildEnvironment(TestCase): 'version': self.version.pk, 'success': True, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'length': mock.ANY, 'error': '', 'setup': '', @@ -169,7 +169,7 @@ class TestLocalBuildEnvironment(TestCase): self.assertTrue(build_env.done) self.assertTrue(build_env.successful) self.assertEqual(len(build_env.commands), 1) - self.assertEqual(build_env.commands[0].output, u'This is okay') + self.assertEqual(build_env.commands[0].output, 'This is okay') # api() is not called anymore, we use api_v2 instead self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called) @@ -189,7 +189,7 @@ class TestLocalBuildEnvironment(TestCase): 'version': self.version.pk, 'success': True, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'length': mock.ANY, 'error': '', 'setup': '', @@ -254,7 +254,7 @@ class TestLocalBuildEnvironment(TestCase): self.assertTrue(build_env.done) self.assertTrue(build_env.failed) self.assertEqual(len(build_env.commands), 1) - self.assertEqual(build_env.commands[0].output, u'This is not okay') + self.assertEqual(build_env.commands[0].output, 'This is not okay') # api() is not called anymore, we use api_v2 instead self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called) @@ -274,7 +274,7 @@ class TestLocalBuildEnvironment(TestCase): 'version': self.version.pk, 'success': False, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'length': mock.ANY, 'error': '', 'setup': '', @@ -537,13 +537,13 @@ class TestDockerBuildEnvironment(TestCase): 'version': self.version.pk, 'success': False, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'exit_code': 1, 'length': mock.ANY, 'error': 'Build environment creation failed', - 'setup': u'', - 'output': u'', - 'state': u'finished', + 'setup': '', + 'output': '', + 'state': 'finished', 'builder': mock.ANY, }) @@ -588,13 +588,13 @@ class TestDockerBuildEnvironment(TestCase): 'version': self.version.pk, 'success': False, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'exit_code': -1, 'length': mock.ANY, 'error': '', - 'setup': u'', - 'output': u'', - 'state': u'finished', + 'setup': '', + 'output': '', + 'state': 'finished', 'builder': mock.ANY, }) @@ -694,7 +694,7 @@ class TestDockerBuildEnvironment(TestCase): container='build-123-project-6-pip', cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True) self.assertEqual(build_env.commands[0].exit_code, 1) - self.assertEqual(build_env.commands[0].output, u'This is the return') + self.assertEqual(build_env.commands[0].output, 'This is the return') self.assertEqual(build_env.commands[0].error, None) self.assertTrue(build_env.failed) @@ -789,7 +789,7 @@ class TestDockerBuildEnvironment(TestCase): container='build-123-project-6-pip', cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True) self.assertEqual(build_env.commands[0].exit_code, 0) - self.assertEqual(build_env.commands[0].output, u'This is the return') + self.assertEqual(build_env.commands[0].output, 'This is the return') self.assertEqual(build_env.commands[0].error, None) self.assertFalse(build_env.failed) @@ -867,12 +867,12 @@ class TestDockerBuildEnvironment(TestCase): 'error': '', 'success': True, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'exit_code': 0, 'length': 0, - 'setup': u'', - 'output': u'', - 'state': u'finished', + 'setup': '', + 'output': '', + 'state': 'finished', 'builder': mock.ANY, }) @@ -970,13 +970,13 @@ class TestDockerBuildEnvironment(TestCase): 'version': self.version.pk, 'success': False, 'project': self.project.pk, - 'setup_error': u'', + 'setup_error': '', 'exit_code': 1, 'length': 0, 'error': 'Build exited due to time out', - 'setup': u'', - 'output': u'', - 'state': u'finished', + 'setup': '', + 'output': '', + 'state': 'finished', 'builder': mock.ANY, }) @@ -1068,7 +1068,7 @@ class TestBuildCommand(TestCase): cmd.run() self.assertEqual( cmd.output, - u'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') + 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') class TestDockerBuildCommand(TestCase): @@ -1117,7 +1117,7 @@ class TestDockerBuildCommand(TestCase): cmd.run() self.assertEqual( cmd.output, - u'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') + 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') self.assertEqual(self.mocks.docker_client.exec_start.call_count, 1) self.assertEqual(self.mocks.docker_client.exec_create.call_count, 1) self.assertEqual(self.mocks.docker_client.exec_inspect.call_count, 1) @@ -1402,7 +1402,7 @@ class TestPythonEnvironment(TestCase): self.build_env_mock.run.assert_not_called() -class AutoWipeEnvironmentBase(object): +class AutoWipeEnvironmentBase: fixtures = ['test_data'] build_env_class = None diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py index 205fa64e7..d3c66552b 100644 --- a/readthedocs/rtd_tests/tests/test_extend.py +++ b/readthedocs/rtd_tests/tests/test_extend.py @@ -7,7 +7,7 @@ from readthedocs.core.utils.extend import (SettingsOverrideObject, # Top level to ensure module name is correct -class FooBase(object): +class FooBase: def bar(self): return 1 diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py index 51006b7e7..17f9b2912 100644 --- a/readthedocs/rtd_tests/tests/test_integrations.py +++ b/readthedocs/rtd_tests/tests/test_integrations.py @@ -30,7 +30,7 @@ class HttpExchangeTests(TestCase): integration_type=Integration.GITHUB_WEBHOOK, provider_data='') resp = client.post( - '/api/v2/webhook/github/{0}/'.format(project.slug), + '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'exchange_json'}, format='json' ) @@ -41,8 +41,8 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.request_headers, - {u'Content-Type': u'application/json; charset=None', - u'Cookie': u''} + {'Content-Type': 'application/json; charset=None', + 'Cookie': ''} ) self.assertEqual( exchange.response_body, @@ -51,8 +51,8 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.response_headers, - {u'Allow': u'POST, OPTIONS', - u'Content-Type': u'text/html; charset=utf-8'} + {'Allow': 'POST, OPTIONS', + 'Content-Type': 'text/html; charset=utf-8'} ) def test_exchange_form_request_body(self): @@ -63,7 +63,7 @@ class HttpExchangeTests(TestCase): integration_type=Integration.GITHUB_WEBHOOK, provider_data='') resp = client.post( - '/api/v2/webhook/github/{0}/'.format(project.slug), + '/api/v2/webhook/github/{}/'.format(project.slug), 'payload=%7B%22ref%22%3A+%22exchange_form%22%7D', content_type='application/x-www-form-urlencoded', ) @@ -74,8 +74,8 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.request_headers, - {u'Content-Type': u'application/x-www-form-urlencoded', - u'Cookie': u''} + {'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': ''} ) self.assertEqual( exchange.response_body, @@ -84,8 +84,8 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.response_headers, - {u'Allow': u'POST, OPTIONS', - u'Content-Type': u'text/html; charset=utf-8'} + {'Allow': 'POST, OPTIONS', + 'Content-Type': 'text/html; charset=utf-8'} ) def test_extraneous_exchanges_deleted_in_correct_order(self): @@ -103,13 +103,13 @@ class HttpExchangeTests(TestCase): for _ in range(10): resp = client.post( - '/api/v2/webhook/github/{0}/'.format(project.slug), + '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'deleted'}, format='json' ) for _ in range(10): resp = client.post( - '/api/v2/webhook/github/{0}/'.format(project.slug), + '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'preserved'}, format='json' ) @@ -134,7 +134,7 @@ class HttpExchangeTests(TestCase): integration_type=Integration.GITHUB_WEBHOOK, provider_data='') resp = client.post( - '/api/v2/webhook/github/{0}/'.format(project.slug), + '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'exchange_json'}, format='json', HTTP_X_FORWARDED_FOR='1.2.3.4', @@ -144,9 +144,9 @@ class HttpExchangeTests(TestCase): exchange = HttpExchange.objects.get(integrations=integration) self.assertEqual( exchange.request_headers, - {u'Content-Type': u'application/json; charset=None', - u'Cookie': u'', - u'X-Foo': u'bar'} + {'Content-Type': 'application/json; charset=None', + 'Cookie': '', + 'X-Foo': 'bar'} ) diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 2213b0d8c..adae07e49 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -43,7 +43,7 @@ class NotificationTests(TestCase): self.assertEqual(notify.get_template_names('site'), ['builds/notifications/foo_site.html']) self.assertEqual(notify.get_subject(), - 'This is {0}'.format(build.id)) + 'This is {}'.format(build.id)) self.assertEqual(notify.get_context_data(), {'foo': build, 'production_uri': 'https://readthedocs.org', @@ -82,7 +82,7 @@ class NotificationBackendTests(TestCase): request=mock.ANY, template='core/email/common.txt', context={'content': 'Test'}, - subject=u'This is {}'.format(build.id), + subject='This is {}'.format(build.id), template_html='core/email/common.html', recipient=user.email, ) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index 277c566e2..f793f8c51 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -19,7 +19,7 @@ from readthedocs.oauth.models import RemoteRepository, RemoteOrganization from readthedocs.rtd_tests.utils import create_user -class URLAccessMixin(object): +class URLAccessMixin: default_kwargs = {} response_data = {} @@ -93,10 +93,10 @@ class URLAccessMixin(object): for not_obj in self.context_data: if isinstance(obj, list) or isinstance(obj, set) or isinstance(obj, tuple): self.assertNotIn(not_obj, obj) - print("%s not in %s" % (not_obj, obj)) + print("{} not in {}".format(not_obj, obj)) else: self.assertNotEqual(not_obj, obj) - print("%s is not %s" % (not_obj, obj)) + print("{} is not {}".format(not_obj, obj)) def _test_url(self, urlpatterns): deconstructed_urls = extract_views_from_urlpatterns(urlpatterns) @@ -134,7 +134,7 @@ class URLAccessMixin(object): class ProjectMixin(URLAccessMixin): def setUp(self): - super(ProjectMixin, self).setUp() + super().setUp() self.build = get(Build, project=self.pip) self.tag = get(Tag, slug='coolness') self.subproject = get(Project, slug='sub', language='ja', @@ -307,7 +307,7 @@ class PrivateProjectUnauthAccessTest(PrivateProjectMixin, TestCase): class APIMixin(URLAccessMixin): def setUp(self): - super(APIMixin, self).setUp() + super().setUp() self.build = get(Build, project=self.pip) self.build_command_result = get(BuildCommandResult, project=self.pip) self.domain = get(Domain, url='http://docs.foobar.com', project=self.pip) diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index 679b761a2..bbbd43a11 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -22,7 +22,7 @@ from readthedocs.projects.tasks import finish_inactive_builds from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex -class ProjectMixin(object): +class ProjectMixin: fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index 6d358123b..e7fefb5bb 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -242,10 +242,10 @@ class TestProjectAdvancedForm(TestCase): # This version is created automatically by the project on save self.assertTrue(self.project.versions.filter(slug=LATEST).exists()) self.assertEqual( - set( + { slug for slug, _ in form.fields['default_version'].widget.choices - ), + }, {'latest', 'public-1', 'public-2', 'private', 'protected'}, ) @@ -254,10 +254,10 @@ class TestProjectAdvancedForm(TestCase): # This version is created automatically by the project on save self.assertTrue(self.project.versions.filter(slug=LATEST).exists()) self.assertEqual( - set( + { identifier for identifier, _ in form.fields['default_branch'].widget.choices - ), + }, { None, 'master', 'public-1', 'public-2', 'public-3', 'public/4', 'protected', 'private' diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index 307e4e451..e35ca3874 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -97,7 +97,7 @@ class TempSiteRootTestCase(TestCase): new_callable=mock.PropertyMock ), } - self.patches = dict((key, mock.start()) for (key, mock) in list(self.mocks.items())) + self.patches = {key: mock.start() for (key, mock) in list(self.mocks.items())} self.patches['PublicSymlinkBase.CNAME_ROOT'].return_value = os.path.join( settings.SITE_ROOT, 'public_cname_root' ) @@ -127,10 +127,10 @@ class TempSiteRootTestCase(TestCase): self.assertEqual(filesystem, get_filesystem(settings.SITE_ROOT)) -class BaseSymlinkCnames(object): +class BaseSymlinkCnames: def setUp(self): - super(BaseSymlinkCnames, self).setUp() + super().setUp() self.project = get(Project, slug='kong', privacy_level=self.privacy, main_language_project=None) self.project.versions.update(privacy_level=self.privacy) @@ -303,10 +303,10 @@ class TestPrivateSymlinkCnames(BaseSymlinkCnames, TempSiteRootTestCase): symlink_class = PrivateSymlink -class BaseSubprojects(object): +class BaseSubprojects: def setUp(self): - super(BaseSubprojects, self).setUp() + super().setUp() self.project = get(Project, slug='kong', privacy_level=self.privacy, main_language_project=None) self.project.versions.update(privacy_level=self.privacy) @@ -514,10 +514,10 @@ class TestPrivateSubprojects(BaseSubprojects, TempSiteRootTestCase): symlink_class = PrivateSymlink -class BaseSymlinkTranslations(object): +class BaseSymlinkTranslations: def setUp(self): - super(BaseSymlinkTranslations, self).setUp() + super().setUp() self.project = get(Project, slug='kong', privacy_level=self.privacy, main_language_project=None) self.project.versions.update(privacy_level=self.privacy) @@ -766,10 +766,10 @@ class TestPrivateSymlinkTranslations(BaseSymlinkTranslations, TempSiteRootTestCa symlink_class = PrivateSymlink -class BaseSymlinkSingleVersion(object): +class BaseSymlinkSingleVersion: def setUp(self): - super(BaseSymlinkSingleVersion, self).setUp() + super().setUp() self.project = get(Project, slug='kong', privacy_level=self.privacy, main_language_project=None) self.project.versions.update(privacy_level=self.privacy) @@ -841,10 +841,10 @@ class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TempSiteRootTestC symlink_class = PrivateSymlink -class BaseSymlinkVersions(object): +class BaseSymlinkVersions: def setUp(self): - super(BaseSymlinkVersions, self).setUp() + super().setUp() self.project = get(Project, slug='kong', privacy_level=self.privacy, main_language_project=None) self.project.versions.update(privacy_level=self.privacy) @@ -970,11 +970,11 @@ class TestPrivateSymlinkVersions(BaseSymlinkVersions, TempSiteRootTestCase): class TestPublicSymlinkUnicode(TempSiteRootTestCase): def setUp(self): - super(TestPublicSymlinkUnicode, self).setUp() - self.project = get(Project, slug='kong', name=u'foo-∫', + super().setUp() + self.project = get(Project, slug='kong', name='foo-∫', main_language_project=None) self.project.save() - self.stable = get(Version, slug='foo-a', verbose_name=u'foo-∂', + self.stable = get(Version, slug='foo-a', verbose_name='foo-∂', active=True, project=self.project) self.symlink = PublicSymlink(self.project) @@ -1035,7 +1035,7 @@ class TestPublicSymlinkUnicode(TempSiteRootTestCase): class TestPublicPrivateSymlink(TempSiteRootTestCase): def setUp(self): - super(TestPublicPrivateSymlink, self).setUp() + super().setUp() from django.contrib.auth.models import User self.user = get(User) diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py index c6e4a93f8..124fd018c 100644 --- a/readthedocs/rtd_tests/tests/test_project_views.py +++ b/readthedocs/rtd_tests/tests/test_project_views.py @@ -32,7 +32,7 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): url = '/dashboard/import/manual/' def setUp(self): - super(TestProfileMiddleware, self).setUp() + super().setUp() data = { 'basics': { 'name': 'foobar', @@ -47,9 +47,9 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): } self.data = {} for key in data: - self.data.update({('{0}-{1}'.format(key, k), v) + self.data.update({('{}-{}'.format(key, k), v) for (k, v) in list(data[key].items())}) - self.data['{0}-current_step'.format(self.wizard_class_slug)] = 'extra' + self.data['{}-current_step'.format(self.wizard_class_slug)] = 'extra' def test_profile_middleware_no_profile(self): """User without profile and isn't banned""" @@ -101,7 +101,7 @@ class TestBasicsForm(WizardTestCase): def request(self, *args, **kwargs): kwargs['user'] = self.user - return super(TestBasicsForm, self).request(*args, **kwargs) + return super().request(*args, **kwargs) def test_form_pass(self): """Only submit the basics""" @@ -146,7 +146,7 @@ class TestBasicsForm(WizardTestCase): class TestAdvancedForm(TestBasicsForm): def setUp(self): - super(TestAdvancedForm, self).setUp() + super().setUp() self.step_data['basics']['advanced'] = True self.step_data['extra'] = { 'description': 'Describe foobar', @@ -169,10 +169,9 @@ class TestAdvancedForm(TestBasicsForm): data = self.step_data['basics'] del data['advanced'] del self.step_data['extra']['tags'] - six.assertCountEqual( - self, + self.assertCountEqual( [tag.name for tag in proj.tags.all()], - [u'bar', u'baz', u'foo']) + ['bar', 'baz', 'foo']) data.update(self.step_data['extra']) for (key, val) in list(data.items()): self.assertEqual(getattr(proj, key), val) diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py index 1ef55d564..841da05c1 100644 --- a/readthedocs/rtd_tests/tests/test_resolver.py +++ b/readthedocs/rtd_tests/tests/test_resolver.py @@ -627,7 +627,7 @@ class ResolverTests(ResolverBase): self.assertEqual(url, 'http://pip.readthedocs.io/en/latest/') -class ResolverAltSetUp(object): +class ResolverAltSetUp: def setUp(self): with mock.patch('readthedocs.projects.models.broadcast'): diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py index d70e0be8b..cb4204e28 100644 --- a/readthedocs/rtd_tests/tests/test_urls.py +++ b/readthedocs/rtd_tests/tests/test_urls.py @@ -48,7 +48,7 @@ class TestVersionURLs(TestCase): def test_version_url_with_caps(self): url = reverse( 'project_download_media', - kwargs={'type_': 'pdf', 'version_slug': u'1.4.X', 'project_slug': u'django'} + kwargs={'type_': 'pdf', 'version_slug': '1.4.X', 'project_slug': 'django'} ) self.assertTrue(url) diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py index e9ffd833e..943843c22 100644 --- a/readthedocs/rtd_tests/tests/test_version_commit_name.py +++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py @@ -20,44 +20,44 @@ class VersionCommitNameTests(TestCase): self.assertEqual(version.identifier_friendly, unicode_name) def test_branch_name_made_friendly_when_sha(self): - commit_hash = u'3d92b728b7d7b842259ac2020c2fa389f13aff0d' + commit_hash = '3d92b728b7d7b842259ac2020c2fa389f13aff0d' version = new(Version, identifier=commit_hash, slug=STABLE, verbose_name=STABLE, type=TAG) # we shorten commit hashes to keep things readable self.assertEqual(version.identifier_friendly, '3d92b728') def test_branch_name(self): - version = new(Version, identifier=u'release-2.5.x', - slug=u'release-2.5.x', verbose_name=u'release-2.5.x', + version = new(Version, identifier='release-2.5.x', + slug='release-2.5.x', verbose_name='release-2.5.x', type=BRANCH) self.assertEqual(version.commit_name, 'release-2.5.x') def test_tag_name(self): - version = new(Version, identifier=u'10f1b29a2bd2', slug=u'release-2.5.0', - verbose_name=u'release-2.5.0', type=TAG) - self.assertEqual(version.commit_name, u'release-2.5.0') + version = new(Version, identifier='10f1b29a2bd2', slug='release-2.5.0', + verbose_name='release-2.5.0', type=TAG) + self.assertEqual(version.commit_name, 'release-2.5.0') def test_branch_with_name_stable(self): - version = new(Version, identifier=u'origin/stable', slug=STABLE, - verbose_name=u'stable', type=BRANCH) - self.assertEqual(version.commit_name, u'stable') + version = new(Version, identifier='origin/stable', slug=STABLE, + verbose_name='stable', type=BRANCH) + self.assertEqual(version.commit_name, 'stable') def test_stable_version_tag(self): version = new(Version, - identifier=u'3d92b728b7d7b842259ac2020c2fa389f13aff0d', + identifier='3d92b728b7d7b842259ac2020c2fa389f13aff0d', slug=STABLE, verbose_name=STABLE, type=TAG) self.assertEqual(version.commit_name, - u'3d92b728b7d7b842259ac2020c2fa389f13aff0d') + '3d92b728b7d7b842259ac2020c2fa389f13aff0d') def test_hg_latest_branch(self): hg_project = get(Project, repo_type=REPO_TYPE_HG) - version = new(Version, identifier=u'default', slug=LATEST, + version = new(Version, identifier='default', slug=LATEST, verbose_name=LATEST, type=BRANCH, project=hg_project) - self.assertEqual(version.commit_name, u'default') + self.assertEqual(version.commit_name, 'default') def test_git_latest_branch(self): git_project = get(Project, repo_type=REPO_TYPE_GIT) version = new(Version, project=git_project, - identifier=u'origin/master', slug=LATEST, + identifier='origin/master', slug=LATEST, verbose_name=LATEST, type=BRANCH) - self.assertEqual(version.commit_name, u'master') + self.assertEqual(version.commit_name, 'master') diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py index 48e4baecc..189f441d8 100644 --- a/readthedocs/search/indexes.py +++ b/readthedocs/search/indexes.py @@ -25,7 +25,7 @@ from elasticsearch.helpers import bulk_index from django.conf import settings -class Index(object): +class Index: """Base class to define some common methods across indexes.""" @@ -92,7 +92,7 @@ class Index(object): } def timestamped_index(self): - return '{0}-{1}'.format( + return '{}-{}'.format( self._index, timezone.now().strftime('%Y%m%d%H%M%S')) def create_index(self, index=None): diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py index 9b19a7e7c..baa4abe5e 100644 --- a/readthedocs/search/parse_json.py +++ b/readthedocs/search/parse_json.py @@ -57,7 +57,7 @@ def generate_sections_from_pyquery(body): h1_section = body('.section > h1') if h1_section: div = h1_section.parent() - h1_title = h1_section.text().replace(u'¶', '').strip() + h1_title = h1_section.text().replace('¶', '').strip() h1_id = div.attr('id') h1_content = "" next_p = body('h1').next() @@ -79,7 +79,7 @@ def generate_sections_from_pyquery(body): for num in range(len(section_list)): div = section_list.eq(num).parent() header = section_list.eq(num) - title = header.text().replace(u'¶', '').strip() + title = header.text().replace('¶', '').strip() section_id = div.attr('id') content = div.html() yield { @@ -108,7 +108,7 @@ def process_file(filename): return None if 'body' in data and data['body']: body = PyQuery(data['body']) - body_content = body.text().replace(u'¶', '') + body_content = body.text().replace('¶', '') sections.extend(generate_sections_from_pyquery(body)) else: log.info('Unable to index content for: %s', filename) diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py index 07444a731..d5ac18baf 100644 --- a/readthedocs/search/tests/test_views.py +++ b/readthedocs/search/tests/test_views.py @@ -14,7 +14,7 @@ from readthedocs.search.tests.utils import get_search_query_from_project_file @pytest.mark.django_db @pytest.mark.search -class TestElasticSearch(object): +class TestElasticSearch: url = reverse_lazy('search') diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py index a742a3419..ec429e4ac 100644 --- a/readthedocs/search/utils.py +++ b/readthedocs/search/utils.py @@ -207,7 +207,7 @@ def parse_sphinx_sections(content): h1_section = body('.section > h1') if h1_section: div = h1_section.parent() - h1_title = h1_section.text().replace(u'¶', '').strip() + h1_title = h1_section.text().replace('¶', '').strip() h1_id = div.attr('id') h1_content = "" next_p = next(body('h1')) # pylint: disable=stop-iteration-return @@ -229,7 +229,7 @@ def parse_sphinx_sections(content): for num in range(len(section_list)): div = section_list.eq(num).parent() header = section_list.eq(num) - title = header.text().replace(u'¶', '').strip() + title = header.text().replace('¶', '').strip() section_id = div.attr('id') content = div.html() yield { diff --git a/readthedocs/search/views.py b/readthedocs/search/views.py index bac1969e8..e1725f87f 100644 --- a/readthedocs/search/views.py +++ b/readthedocs/search/views.py @@ -14,7 +14,7 @@ from readthedocs.builds.constants import LATEST from readthedocs.search import lib as search_lib log = logging.getLogger(__name__) -LOG_TEMPLATE = u'(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}' +LOG_TEMPLATE = '(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}' UserInput = collections.namedtuple( 'UserInput', diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py index a6a3e8679..205dbdd99 100644 --- a/readthedocs/settings/base.py +++ b/readthedocs/settings/base.py @@ -43,7 +43,7 @@ class CommunityBaseSettings(Settings): PUBLIC_DOMAIN = None PUBLIC_DOMAIN_USES_HTTPS = False USE_SUBDOMAIN = False - PUBLIC_API_URL = 'https://{0}'.format(PRODUCTION_DOMAIN) + PUBLIC_API_URL = 'https://{}'.format(PRODUCTION_DOMAIN) # Email DEFAULT_FROM_EMAIL = 'no-reply@readthedocs.org' diff --git a/readthedocs/settings/dev.py b/readthedocs/settings/dev.py index 7fa4dafe9..8fd9860a0 100644 --- a/readthedocs/settings/dev.py +++ b/readthedocs/settings/dev.py @@ -50,7 +50,7 @@ class CommunityDevSettings(CommunityBaseSettings): @property def LOGGING(self): # noqa - avoid pep8 N802 - logging = super(CommunityDevSettings, self).LOGGING + logging = super().LOGGING logging['formatters']['default']['format'] = '[%(asctime)s] ' + self.LOG_FORMAT # Allow Sphinx and other tools to create loggers logging['disable_existing_loggers'] = False diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py index 5ea817c11..9a309a807 100644 --- a/readthedocs/vcs_support/backends/bzr.py +++ b/readthedocs/vcs_support/backends/bzr.py @@ -26,7 +26,7 @@ class Backend(BaseVCS): fallback_branch = '' def update(self): - super(Backend, self).update() + super().update() retcode = self.run('bzr', 'status', record=False)[0] if retcode == 0: return self.up() @@ -90,7 +90,7 @@ class Backend(BaseVCS): return stdout.strip() def checkout(self, identifier=None): - super(Backend, self).checkout() + super().checkout() if not identifier: return self.up() return self.run('bzr', 'switch', identifier) diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index f96087995..80fafb89e 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -36,7 +36,7 @@ class Backend(BaseVCS): repo_depth = 50 def __init__(self, *args, **kwargs): - super(Backend, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.token = kwargs.get('token', None) self.repo_url = self._get_clone_url() @@ -46,7 +46,7 @@ class Backend(BaseVCS): hacked_url = re.sub('.git$', '', hacked_url) clone_url = 'https://%s' % hacked_url if self.token: - clone_url = 'https://%s@%s' % (self.token, hacked_url) + clone_url = 'https://{}@{}'.format(self.token, hacked_url) return clone_url # Don't edit URL because all hosts aren't the same # else: @@ -58,7 +58,7 @@ class Backend(BaseVCS): def update(self): """Clone or update the repository.""" - super(Backend, self).update() + super().update() if self.repo_exists(): self.set_remote_url(self.repo_url) return self.fetch() @@ -228,7 +228,7 @@ class Backend(BaseVCS): def checkout(self, identifier=None): """Checkout to identifier or latest.""" - super(Backend, self).checkout() + super().checkout() # Find proper identifier if not identifier: identifier = self.default_branch or self.fallback_branch @@ -291,7 +291,7 @@ class Backend(BaseVCS): @property def env(self): - env = super(Backend, self).env + env = super().env env['GIT_DIR'] = os.path.join(self.working_dir, '.git') # Don't prompt for username, this requires Git 2.3+ env['GIT_TERMINAL_PROMPT'] = '0' diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py index 4a66624f1..e65d4c650 100644 --- a/readthedocs/vcs_support/backends/hg.py +++ b/readthedocs/vcs_support/backends/hg.py @@ -20,7 +20,7 @@ class Backend(BaseVCS): fallback_branch = 'default' def update(self): - super(Backend, self).update() + super().update() retcode = self.run('hg', 'status', record=False)[0] if retcode == 0: return self.pull() @@ -107,7 +107,7 @@ class Backend(BaseVCS): return stdout.strip() def checkout(self, identifier=None): - super(Backend, self).checkout() + super().checkout() if not identifier: identifier = 'tip' return self.run('hg', 'update', '--clean', identifier) diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py index 61e293242..2363116c6 100644 --- a/readthedocs/vcs_support/backends/svn.py +++ b/readthedocs/vcs_support/backends/svn.py @@ -25,7 +25,7 @@ class Backend(BaseVCS): fallback_branch = '/trunk/' def __init__(self, *args, **kwargs): - super(Backend, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.repo_url[-1] != '/': self.base_url = self.repo_url self.repo_url += '/' @@ -36,7 +36,7 @@ class Backend(BaseVCS): self.base_url = self.repo_url def update(self): - super(Backend, self).update() + super().update() # For some reason `svn status` gives me retcode 0 in non-svn # directories that's why I use `svn info` here. retcode, _, _ = self.run('svn', 'info', record=False) @@ -102,7 +102,7 @@ class Backend(BaseVCS): return stdout.strip() def checkout(self, identifier=None): - super(Backend, self).checkout() + super().checkout() return self.co(identifier) def get_url(self, base_url, identifier): diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py index 0e632e3b8..f430fd0d0 100644 --- a/readthedocs/vcs_support/base.py +++ b/readthedocs/vcs_support/base.py @@ -12,7 +12,7 @@ from builtins import object log = logging.getLogger(__name__) -class VCSVersion(object): +class VCSVersion: """ Represents a Version (tag or branch) in a VCS. @@ -29,11 +29,11 @@ class VCSVersion(object): self.verbose_name = verbose_name def __repr__(self): - return ' Date: Thu, 3 Jan 2019 17:04:44 +0100 Subject: [PATCH 08/65] Use unicode string for Sphinx config file This file is generated by our code and can be used by Python2 when building the documentation, so we need to keep the `u` before the definition of the string. --- readthedocs/rtd_tests/files/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 11f872849..b448eab9a 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -13,7 +13,7 @@ source_parsers = { '.md': CommonMarkParser, } master_doc = 'index' -project = 'Pip' +project = u'Pip' copyright = str(datetime.now().year) version = '0.8.1' release = '0.8.1' From 1b589392703721642ecb4ebcb0b23663a2c1ef4e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 17:26:23 +0100 Subject: [PATCH 09/65] Remove unused imports to avoid lint issues --- readthedocs/config/config.py | 2 -- readthedocs/config/validation.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index ac6b3db99..900fe637e 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -9,8 +9,6 @@ import os import re from contextlib import contextmanager -import six - from readthedocs.projects.constants import DOCUMENTATION_CHOICES from .find import find_one diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index b1b42fc67..baf7808d6 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -3,8 +3,6 @@ from __future__ import division, print_function, unicode_literals import os -from six import string_types, text_type - INVALID_BOOL = 'invalid-bool' INVALID_CHOICE = 'invalid-choice' INVALID_LIST = 'invalid-list' From 271884a6649b3d0f8e1ee31b6ef22684f8dc1a0f Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 17:30:05 +0100 Subject: [PATCH 10/65] Run tests with python3.6 on Travis --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bce5b2f37..e5083ec7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ env: - ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz matrix: include: + - python: 3.6 + env: TOXENV=py36 - python: 3.6 env: TOXENV=docs - python: 3.6 @@ -44,6 +46,6 @@ notifications: branches: only: - - master + - master - rel # Community release branch - relcorp # Corporate release branch From 849abb3cc28dc369fe5db4a5eb6365e15cb97cdd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Jan 2019 17:30:20 +0100 Subject: [PATCH 11/65] Do not include search tests when running migrations env --- scripts/travis/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis/run_tests.sh b/scripts/travis/run_tests.sh index 99c563870..c5da9d551 100755 --- a/scripts/travis/run_tests.sh +++ b/scripts/travis/run_tests.sh @@ -1,4 +1,4 @@ -if ! [[ "$TOXENV" =~ ^(docs|lint|eslint) ]]; +if ! [[ "$TOXENV" =~ ^(docs|lint|eslint|migrations) ]]; then args="'--including-search'" fi From c78187b62fb00113405ec4f433caf7122dbad49c Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 20:53:00 +0100 Subject: [PATCH 12/65] isort all the files $ pre-commit run python-import-sorter --files `find readthedocs -name '*.py'` It was ran with this configuration: https://github.com/rtfd/common/pull/28 --- readthedocs/analytics/apps.py | 1 - readthedocs/analytics/tasks.py | 2 -- readthedocs/analytics/tests.py | 4 --- readthedocs/analytics/utils.py | 8 +++--- readthedocs/analytics/vendor/ipaddress.py | 4 +-- readthedocs/api/base.py | 5 +--- readthedocs/api/client.py | 6 ++-- readthedocs/api/utils.py | 4 +-- readthedocs/builds/admin.py | 4 +-- readthedocs/builds/constants.py | 4 +-- readthedocs/builds/forms.py | 8 ------ readthedocs/builds/managers.py | 19 +++++++++---- readthedocs/builds/models.py | 11 ++------ readthedocs/builds/querysets.py | 2 -- readthedocs/builds/signals.py | 1 - readthedocs/builds/syncers.py | 5 +--- readthedocs/builds/urls.py | 4 +-- readthedocs/builds/utils.py | 8 +++--- readthedocs/builds/version_slug.py | 3 -- readthedocs/builds/views.py | 8 ------ readthedocs/config/config.py | 3 +- readthedocs/config/find.py | 2 -- readthedocs/config/models.py | 2 -- readthedocs/config/parser.py | 3 +- readthedocs/config/tests/test_config.py | 2 -- readthedocs/config/tests/test_find.py | 6 ++-- readthedocs/config/tests/test_parser.py | 3 -- readthedocs/config/tests/test_utils.py | 3 -- readthedocs/config/tests/test_validation.py | 24 ++++++++++------ readthedocs/config/tests/utils.py | 2 -- readthedocs/config/validation.py | 3 +- readthedocs/constants.py | 1 - readthedocs/core/adapters.py | 2 +- readthedocs/core/admin.py | 5 ++-- readthedocs/core/apps.py | 1 - readthedocs/core/backends.py | 3 +- readthedocs/core/context_processors.py | 1 - readthedocs/core/fields.py | 1 - readthedocs/core/forms.py | 5 +--- .../core/management/commands/archive.py | 7 ++--- .../core/management/commands/clean_builds.py | 4 +-- .../core/management/commands/import_github.py | 3 +- .../commands/import_github_language.py | 9 +++--- .../commands/provision_elasticsearch.py | 9 ++++-- readthedocs/core/management/commands/pull.py | 1 - .../commands/reindex_elasticsearch.py | 5 ++-- .../core/management/commands/set_metadata.py | 4 +-- .../core/management/commands/symlink.py | 3 +- .../core/management/commands/update_api.py | 1 - .../core/management/commands/update_repos.py | 4 +-- .../management/commands/update_versions.py | 1 - readthedocs/core/middleware.py | 6 ++-- readthedocs/core/mixins.py | 4 +-- readthedocs/core/models.py | 10 +++---- readthedocs/core/permissions.py | 2 -- readthedocs/core/resolver.py | 4 +-- readthedocs/core/settings.py | 2 -- readthedocs/core/signals.py | 10 +++---- readthedocs/core/static.py | 2 -- readthedocs/core/symlink.py | 9 +----- readthedocs/core/tasks.py | 3 +- readthedocs/core/templatetags/core_tags.py | 3 -- readthedocs/core/templatetags/privacy_tags.py | 2 -- readthedocs/core/tests/test_signals.py | 4 +-- readthedocs/core/urls/single_version.py | 5 ++-- readthedocs/core/urls/subdomain.py | 15 ++++------ readthedocs/core/utils/extend.py | 2 -- readthedocs/core/utils/tasks/public.py | 8 +----- readthedocs/core/utils/tasks/retrieve.py | 8 +----- readthedocs/core/views/hooks.py | 8 +----- readthedocs/core/views/serve.py | 9 ++---- readthedocs/doc_builder/backends/mkdocs.py | 4 +-- readthedocs/doc_builder/backends/sphinx.py | 9 ++---- readthedocs/doc_builder/base.py | 5 +--- readthedocs/doc_builder/config.py | 3 -- readthedocs/doc_builder/constants.py | 4 +-- readthedocs/doc_builder/environments.py | 9 ------ readthedocs/doc_builder/exceptions.py | 2 -- readthedocs/doc_builder/loader.py | 2 +- .../doc_builder/python_environments.py | 10 +------ readthedocs/doc_builder/signals.py | 3 +- readthedocs/gold/admin.py | 2 +- readthedocs/gold/apps.py | 1 - readthedocs/gold/forms.py | 4 --- readthedocs/gold/models.py | 8 +----- readthedocs/gold/signals.py | 1 - readthedocs/gold/tests/test_forms.py | 8 ++---- readthedocs/gold/tests/test_signals.py | 6 ++-- readthedocs/gold/urls.py | 1 - readthedocs/gold/views.py | 9 +----- readthedocs/integrations/admin.py | 5 ++-- readthedocs/integrations/models.py | 8 ------ readthedocs/notifications/apps.py | 1 - readthedocs/notifications/backends.py | 4 --- readthedocs/notifications/constants.py | 1 - readthedocs/notifications/forms.py | 1 - readthedocs/notifications/notification.py | 9 +++--- readthedocs/notifications/storages.py | 9 +++--- readthedocs/notifications/urls.py | 4 +-- readthedocs/notifications/views.py | 3 +- readthedocs/oauth/admin.py | 3 +- readthedocs/oauth/models.py | 6 +--- readthedocs/oauth/notifications.py | 2 -- readthedocs/oauth/querysets.py | 2 -- readthedocs/oauth/services/base.py | 5 +--- readthedocs/oauth/services/bitbucket.py | 9 +++--- readthedocs/oauth/services/github.py | 9 +++--- readthedocs/oauth/services/gitlab.py | 3 +- readthedocs/oauth/tasks.py | 8 +----- readthedocs/oauth/utils.py | 10 ++++--- readthedocs/payments/forms.py | 8 ++---- readthedocs/payments/mixins.py | 2 -- readthedocs/payments/utils.py | 2 +- readthedocs/profiles/urls/private.py | 7 ----- readthedocs/profiles/urls/public.py | 1 - readthedocs/profiles/views.py | 9 +----- readthedocs/projects/admin.py | 7 ----- readthedocs/projects/backends/views.py | 1 - readthedocs/projects/constants.py | 4 +-- readthedocs/projects/exceptions.py | 2 -- readthedocs/projects/feeds.py | 1 - readthedocs/projects/forms.py | 8 ------ .../commands/import_project_from_live.py | 8 +++--- readthedocs/projects/models.py | 19 +++++++------ readthedocs/projects/notifications.py | 1 - readthedocs/projects/querysets.py | 2 -- readthedocs/projects/signals.py | 1 - readthedocs/projects/tasks.py | 13 ++------- .../projects/templatetags/projects_tags.py | 1 - readthedocs/projects/urls/private.py | 24 +++++++++++----- readthedocs/projects/urls/public.py | 6 ++-- readthedocs/projects/utils.py | 9 +----- readthedocs/projects/validators.py | 4 +-- readthedocs/projects/version_handling.py | 9 +++--- readthedocs/projects/views/base.py | 7 ++--- readthedocs/projects/views/mixins.py | 2 -- readthedocs/projects/views/private.py | 8 +----- readthedocs/projects/views/public.py | 6 ++-- readthedocs/redirects/admin.py | 3 +- readthedocs/redirects/managers.py | 1 - readthedocs/redirects/models.py | 8 +++--- readthedocs/redirects/utils.py | 4 +-- readthedocs/restapi/client.py | 8 +----- readthedocs/restapi/permissions.py | 2 -- readthedocs/restapi/serializers.py | 6 +--- readthedocs/restapi/signals.py | 3 +- readthedocs/restapi/urls.py | 8 +----- readthedocs/restapi/utils.py | 8 +----- readthedocs/restapi/views/core_views.py | 7 ++--- readthedocs/restapi/views/footer_views.py | 8 ++---- readthedocs/restapi/views/integrations.py | 9 +----- readthedocs/restapi/views/model_views.py | 27 ++++++++++++------ readthedocs/restapi/views/search_views.py | 3 +- readthedocs/restapi/views/task_views.py | 8 +----- readthedocs/rtd_tests/base.py | 9 ++---- readthedocs/rtd_tests/files/conf.py | 2 -- readthedocs/rtd_tests/mocks/environment.py | 3 -- readthedocs/rtd_tests/mocks/mock_api.py | 5 ++-- readthedocs/rtd_tests/mocks/paths.py | 3 +- .../tests/projects/test_admin_actions.py | 5 ++-- readthedocs/rtd_tests/tests/test_api.py | 10 +------ .../rtd_tests/tests/test_api_permissions.py | 5 ++-- .../tests/test_api_version_compare.py | 2 -- readthedocs/rtd_tests/tests/test_backend.py | 9 ------ .../rtd_tests/tests/test_backend_svn.py | 13 ++------- .../rtd_tests/tests/test_build_config.py | 6 ++-- .../rtd_tests/tests/test_build_forms.py | 3 -- .../tests/test_build_notifications.py | 8 ++---- readthedocs/rtd_tests/tests/test_builds.py | 11 ++------ readthedocs/rtd_tests/tests/test_celery.py | 19 ++++++------- .../tests/test_config_integration.py | 4 --- readthedocs/rtd_tests/tests/test_core_tags.py | 5 +--- .../rtd_tests/tests/test_core_utils.py | 9 ++---- .../rtd_tests/tests/test_doc_builder.py | 4 --- .../rtd_tests/tests/test_doc_building.py | 9 ------ .../rtd_tests/tests/test_doc_serving.py | 13 ++++----- readthedocs/rtd_tests/tests/test_domains.py | 5 +--- readthedocs/rtd_tests/tests/test_extend.py | 9 +++--- readthedocs/rtd_tests/tests/test_footer.py | 8 ------ readthedocs/rtd_tests/tests/test_gold.py | 10 ++----- .../rtd_tests/tests/test_imported_file.py | 4 +-- .../rtd_tests/tests/test_integrations.py | 13 ++++----- .../rtd_tests/tests/test_middleware.py | 14 +++------- .../rtd_tests/tests/test_notifications.py | 14 ++++++---- readthedocs/rtd_tests/tests/test_oauth.py | 9 +++--- .../rtd_tests/tests/test_post_commit_hooks.py | 5 +--- readthedocs/rtd_tests/tests/test_privacy.py | 13 ++++----- .../rtd_tests/tests/test_privacy_urls.py | 10 ++----- .../rtd_tests/tests/test_profile_views.py | 5 +--- readthedocs/rtd_tests/tests/test_project.py | 12 ++++---- .../rtd_tests/tests/test_project_forms.py | 8 ------ .../rtd_tests/tests/test_project_querysets.py | 11 ++++---- .../rtd_tests/tests/test_project_symlinks.py | 15 +++++----- .../rtd_tests/tests/test_project_views.py | 28 +++++++++---------- readthedocs/rtd_tests/tests/test_redirects.py | 10 ++----- .../rtd_tests/tests/test_repo_parsing.py | 4 --- readthedocs/rtd_tests/tests/test_resolver.py | 9 +++--- .../rtd_tests/tests/test_restapi_client.py | 4 --- .../tests/test_search_json_parsing.py | 1 - .../rtd_tests/tests/test_single_version.py | 5 +--- .../rtd_tests/tests/test_subprojects.py | 5 +--- .../rtd_tests/tests/test_sync_versions.py | 10 +------ readthedocs/rtd_tests/tests/test_urls.py | 5 +--- .../tests/test_version_commit_name.py | 13 ++------- .../rtd_tests/tests/test_version_config.py | 3 -- .../rtd_tests/tests/test_version_slug.py | 6 ++-- readthedocs/rtd_tests/tests/test_views.py | 9 +----- readthedocs/rtd_tests/utils.py | 7 ----- readthedocs/search/indexes.py | 7 +---- readthedocs/search/lib.py | 14 +++++----- readthedocs/search/parse_json.py | 6 ++-- readthedocs/search/signals.py | 2 +- readthedocs/search/tests/conftest.py | 9 ++++-- readthedocs/search/tests/test_views.py | 1 - readthedocs/search/tests/utils.py | 1 - readthedocs/search/utils.py | 11 +++----- readthedocs/search/views.py | 4 +-- readthedocs/urls.py | 20 +++++++------ readthedocs/vcs_support/backends/bzr.py | 10 ------- readthedocs/vcs_support/backends/git.py | 9 +----- readthedocs/vcs_support/backends/hg.py | 7 ----- readthedocs/vcs_support/backends/svn.py | 10 ------- readthedocs/vcs_support/base.py | 4 --- readthedocs/vcs_support/tests.py | 4 --- readthedocs/vcs_support/utils.py | 3 -- readthedocs/worker.py | 2 -- 226 files changed, 417 insertions(+), 976 deletions(-) diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py index afdea7f5d..219a787dc 100644 --- a/readthedocs/analytics/apps.py +++ b/readthedocs/analytics/apps.py @@ -1,6 +1,5 @@ """Django app config for the analytics app.""" -from __future__ import absolute_import from django.apps import AppConfig diff --git a/readthedocs/analytics/tasks.py b/readthedocs/analytics/tasks.py index 6c1ec2cfc..b79102fc6 100644 --- a/readthedocs/analytics/tasks.py +++ b/readthedocs/analytics/tasks.py @@ -1,7 +1,5 @@ """Tasks for Read the Docs' analytics""" -from __future__ import absolute_import - from django.conf import settings import readthedocs diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 37b269570..934355516 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -1,10 +1,7 @@ -from __future__ import absolute_import, unicode_literals - from django.test import TestCase from .utils import anonymize_ip_address, anonymize_user_agent - class UtilsTests(TestCase): def test_anonymize_ip(self): self.assertEqual(anonymize_ip_address('127.0.0.1'), '127.0.0.0') @@ -29,4 +26,3 @@ class UtilsTests(TestCase): anonymize_user_agent('Some rare user agent'), 'Rare user agent', ) - diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py index 44eef5511..52c44d5cd 100644 --- a/readthedocs/analytics/utils.py +++ b/readthedocs/analytics/utils.py @@ -1,15 +1,15 @@ """Utilities related to analytics""" -from __future__ import absolute_import, unicode_literals import hashlib import logging -from django.conf import settings -from django.utils.encoding import force_text, force_bytes -from django.utils.crypto import get_random_string import requests +from django.conf import settings +from django.utils.crypto import get_random_string +from django.utils.encoding import force_bytes, force_text from user_agents import parse + try: # Python 3.3+ only import ipaddress diff --git a/readthedocs/analytics/vendor/ipaddress.py b/readthedocs/analytics/vendor/ipaddress.py index b81d477bf..8014d2ef7 100644 --- a/readthedocs/analytics/vendor/ipaddress.py +++ b/readthedocs/analytics/vendor/ipaddress.py @@ -9,12 +9,10 @@ and networks. """ -from __future__ import unicode_literals - - import itertools import struct + __version__ = '1.0.22' # Compatibility functions diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py index 8e90c279b..75e984d42 100644 --- a/readthedocs/api/base.py +++ b/readthedocs/api/base.py @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- """API resources.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging -from builtins import object import redis from django.conf.urls import url @@ -25,6 +21,7 @@ from readthedocs.projects.models import ImportedFile, Project from .utils import PostAuthentication + log = logging.getLogger(__name__) diff --git a/readthedocs/api/client.py b/readthedocs/api/client.py index dd742198b..a30201634 100644 --- a/readthedocs/api/client.py +++ b/readthedocs/api/client.py @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- """Slumber API client.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging -from django.conf import settings import requests +from django.conf import settings from requests_toolbelt.adapters import host_header_ssl from slumber import API + log = logging.getLogger(__name__) PRODUCTION_DOMAIN = getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org') diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py index c15cb02ec..e2b5e9c33 100644 --- a/readthedocs/api/utils.py +++ b/readthedocs/api/utils.py @@ -1,13 +1,11 @@ """Utility classes for api module""" -from __future__ import absolute_import import logging from django.utils.translation import ugettext - from tastypie.authentication import BasicAuthentication from tastypie.authorization import Authorization -from tastypie.resources import ModelResource from tastypie.exceptions import NotFound +from tastypie.resources import ModelResource log = logging.getLogger(__name__) diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py index 66c046f9c..59fe235ae 100644 --- a/readthedocs/builds/admin.py +++ b/readthedocs/builds/admin.py @@ -1,10 +1,10 @@ """Django admin interface for `~builds.models.Build` and related models.""" -from __future__ import absolute_import from django.contrib import admin -from readthedocs.builds.models import Build, Version, BuildCommandResult from guardian.admin import GuardedModelAdmin +from readthedocs.builds.models import Build, BuildCommandResult, Version + class BuildCommandResultInline(admin.TabularInline): model = BuildCommandResult diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py index 99d816814..660fb0b0f 100644 --- a/readthedocs/builds/constants.py +++ b/readthedocs/builds/constants.py @@ -1,8 +1,8 @@ """Constants for the builds app.""" -from __future__ import absolute_import -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + BUILD_STATE_TRIGGERED = 'triggered' BUILD_STATE_CLONING = 'cloning' diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index 6e5e94b50..ae655bfc9 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -1,13 +1,5 @@ """Django forms for the builds app.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - -from builtins import object from django import forms from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py index bd9316c3e..bd8866058 100644 --- a/readthedocs/builds/managers.py +++ b/readthedocs/builds/managers.py @@ -1,14 +1,21 @@ """Build and Version class model Managers""" -from __future__ import absolute_import - from django.db import models -from .constants import (BRANCH, TAG, LATEST, LATEST_VERBOSE_NAME, STABLE, - STABLE_VERBOSE_NAME) +from readthedocs.core.utils.extend import ( + SettingsOverrideObject, + get_override_class, +) + +from .constants import ( + BRANCH, + LATEST, + LATEST_VERBOSE_NAME, + STABLE, + STABLE_VERBOSE_NAME, + TAG, +) from .querysets import VersionQuerySet -from readthedocs.core.utils.extend import (SettingsOverrideObject, - get_override_class) __all__ = ['VersionManager'] diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index b27b7e145..c11fa13d0 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1,21 +1,14 @@ # -*- coding: utf-8 -*- """Models for the builds app.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os.path import re from shutil import rmtree -from builtins import object from django.conf import settings from django.db import models +from django.urls import reverse from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext @@ -23,7 +16,6 @@ from django.utils.translation import ugettext_lazy as _ from guardian.shortcuts import assign from jsonfield import JSONField from taggit.managers import TaggableManager -from django.urls import reverse from readthedocs.core.utils import broadcast from readthedocs.projects.constants import ( @@ -55,6 +47,7 @@ from .utils import ( ) from .version_slug import VersionSlugField + DEFAULT_VERSION_PRIVACY_LEVEL = getattr( settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public') diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py index 34408b9d9..a2a349388 100644 --- a/readthedocs/builds/querysets.py +++ b/readthedocs/builds/querysets.py @@ -1,7 +1,5 @@ """Build and Version QuerySet classes""" -from __future__ import absolute_import - from django.db import models from guardian.shortcuts import get_objects_for_user diff --git a/readthedocs/builds/signals.py b/readthedocs/builds/signals.py index c80f7d3e4..c9bb49cc7 100644 --- a/readthedocs/builds/signals.py +++ b/readthedocs/builds/signals.py @@ -1,6 +1,5 @@ """Build signals""" -from __future__ import absolute_import import django.dispatch diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py index cf5a9a90e..5f02f966b 100644 --- a/readthedocs/builds/syncers.py +++ b/readthedocs/builds/syncers.py @@ -5,18 +5,15 @@ Classes to copy files between build and web servers. local machine. """ -from __future__ import absolute_import - import getpass import logging import os import shutil -from builtins import object from django.conf import settings -from readthedocs.core.utils.extend import SettingsOverrideObject from readthedocs.core.utils import safe_makedirs +from readthedocs.core.utils.extend import SettingsOverrideObject log = logging.getLogger(__name__) diff --git a/readthedocs/builds/urls.py b/readthedocs/builds/urls.py index a148362a5..07f787d0b 100644 --- a/readthedocs/builds/urls.py +++ b/readthedocs/builds/urls.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- """URL configuration for builds app.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - from django.conf.urls import url from .views import builds_redirect_detail, builds_redirect_list + urlpatterns = [ url( r'^(?P[-\w]+)/(?P\d+)/$', diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py index 7fcc245db..d2d49270c 100644 --- a/readthedocs/builds/utils.py +++ b/readthedocs/builds/utils.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """Utilities for the builds app.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - from readthedocs.projects.constants import ( - BITBUCKET_REGEXS, GITHUB_REGEXS, GITLAB_REGEXS) + BITBUCKET_REGEXS, + GITHUB_REGEXS, + GITLAB_REGEXS, +) def get_github_username_repo(url): diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py index 7eb71f827..18ad3d19f 100644 --- a/readthedocs/builds/version_slug.py +++ b/readthedocs/builds/version_slug.py @@ -17,8 +17,6 @@ slug. This is used since using numbers in tags is too common and appending another number would be confusing. """ -from __future__ import absolute_import - import math import re import string @@ -26,7 +24,6 @@ from operator import truediv from django.db import models from django.utils.encoding import force_text -from builtins import range def get_fields_with_model(cls): diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py index 680bd271c..37a46ae3e 100644 --- a/readthedocs/builds/views.py +++ b/readthedocs/builds/views.py @@ -2,16 +2,8 @@ """Views for builds app.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging -from builtins import object from django.contrib import messages from django.contrib.auth.decorators import login_required from django.http import ( diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 900fe637e..57e01439e 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -3,8 +3,6 @@ # pylint: disable=too-many-lines """Build configuration for rtd.""" -from __future__ import division, print_function, unicode_literals - import os import re from contextlib import contextmanager @@ -26,6 +24,7 @@ from .validation import ( validate_string, ) + __all__ = ( 'ALL', 'load', diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py index cb3e5e0c5..ed49c765c 100644 --- a/readthedocs/config/find.py +++ b/readthedocs/config/find.py @@ -1,7 +1,5 @@ """Helper functions to search files.""" -from __future__ import division, print_function, unicode_literals - import os import re diff --git a/readthedocs/config/models.py b/readthedocs/config/models.py index bf12ddfa6..ce0604f18 100644 --- a/readthedocs/config/models.py +++ b/readthedocs/config/models.py @@ -1,7 +1,5 @@ """Models for the response of the configuration object.""" -from __future__ import division, print_function, unicode_literals - from collections import namedtuple diff --git a/readthedocs/config/parser.py b/readthedocs/config/parser.py index 655b1601b..85e31782b 100644 --- a/readthedocs/config/parser.py +++ b/readthedocs/config/parser.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- """YAML parser for the RTD configuration file.""" -from __future__ import division, print_function, unicode_literals - import yaml + __all__ = ('parse', 'ParseError') diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 29b776d0f..e3e68110e 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - import os import re import textwrap diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index 7ba651059..dd4715f20 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -1,13 +1,11 @@ -from __future__ import division, print_function, unicode_literals - import os + import pytest -import six + from readthedocs.config.find import find_one from .utils import apply_fs - def test_find_no_files(tmpdir): with tmpdir.as_cwd(): path = find_one(os.getcwd(), r'readthedocs.yml') diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py index 4cbe457bb..37476af91 100644 --- a/readthedocs/config/tests/test_parser.py +++ b/readthedocs/config/tests/test_parser.py @@ -1,12 +1,9 @@ -from __future__ import division, print_function, unicode_literals - from io import StringIO from pytest import raises from readthedocs.config.parser import ParseError, parse - def test_parse_empty_config_file(): buf = StringIO('') with raises(ParseError): diff --git a/readthedocs/config/tests/test_utils.py b/readthedocs/config/tests/test_utils.py index 3d89cb0d0..2332b3071 100644 --- a/readthedocs/config/tests/test_utils.py +++ b/readthedocs/config/tests/test_utils.py @@ -1,8 +1,5 @@ -from __future__ import division, print_function, unicode_literals - from .utils import apply_fs - def test_apply_fs_with_empty_contents(tmpdir): # Doesn't do anything if second parameter is empty. apply_fs(tmpdir, {}) diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py index 6437acfbf..fd7d9e781 100644 --- a/readthedocs/config/tests/test_validation.py +++ b/readthedocs/config/tests/test_validation.py @@ -1,18 +1,26 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - import os from mock import patch from pytest import raises -from six import text_type from readthedocs.config.validation import ( - INVALID_BOOL, INVALID_CHOICE, INVALID_DIRECTORY, INVALID_FILE, INVALID_LIST, - INVALID_PATH, INVALID_STRING, ValidationError, validate_bool, - validate_choice, validate_directory, validate_file, validate_list, - validate_path, validate_string) - + INVALID_BOOL, + INVALID_CHOICE, + INVALID_DIRECTORY, + INVALID_FILE, + INVALID_LIST, + INVALID_PATH, + INVALID_STRING, + ValidationError, + validate_bool, + validate_choice, + validate_directory, + validate_file, + validate_list, + validate_path, + validate_string, +) class TestValidateBool: def test_it_accepts_true(self): diff --git a/readthedocs/config/tests/utils.py b/readthedocs/config/tests/utils.py index b1b312420..1e390fa8a 100644 --- a/readthedocs/config/tests/utils.py +++ b/readthedocs/config/tests/utils.py @@ -1,5 +1,3 @@ -from __future__ import division, print_function, unicode_literals - def apply_fs(tmpdir, contents): """ diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index baf7808d6..a69b8ed77 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -1,8 +1,7 @@ """Validations for the RTD configuration file.""" -from __future__ import division, print_function, unicode_literals - import os + INVALID_BOOL = 'invalid-bool' INVALID_CHOICE = 'invalid-choice' INVALID_LIST = 'invalid-list' diff --git a/readthedocs/constants.py b/readthedocs/constants.py index b9796e8f9..35cca6274 100644 --- a/readthedocs/constants.py +++ b/readthedocs/constants.py @@ -1,6 +1,5 @@ """Common constants""" -from __future__ import absolute_import from readthedocs.builds.version_slug import VERSION_SLUG_REGEX from readthedocs.projects.constants import LANGUAGES_REGEX, PROJECT_SLUG_REGEX diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py index 5bfbf6d5c..803952a79 100644 --- a/readthedocs/core/adapters.py +++ b/readthedocs/core/adapters.py @@ -1,6 +1,5 @@ """Allauth overrides""" -from __future__ import absolute_import import json import logging @@ -9,6 +8,7 @@ from django.template.loader import render_to_string from readthedocs.core.utils import send_email + try: from django.utils.encoding import force_text except ImportError: diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py index b30f54604..5e8042f12 100644 --- a/readthedocs/core/admin.py +++ b/readthedocs/core/admin.py @@ -1,13 +1,12 @@ """Django admin interface for core models.""" -from __future__ import absolute_import from datetime import timedelta from django.contrib import admin -from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin -from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ from readthedocs.core.models import UserProfile from readthedocs.projects.models import Project diff --git a/readthedocs/core/apps.py b/readthedocs/core/apps.py index 4a9875ffb..d422684ba 100644 --- a/readthedocs/core/apps.py +++ b/readthedocs/core/apps.py @@ -1,6 +1,5 @@ """App configurations for core app.""" -from __future__ import absolute_import from django.apps import AppConfig diff --git a/readthedocs/core/backends.py b/readthedocs/core/backends.py index 6a8b8ec90..64258fe91 100644 --- a/readthedocs/core/backends.py +++ b/readthedocs/core/backends.py @@ -1,10 +1,9 @@ """Email backends for core app.""" -from __future__ import absolute_import import smtplib -from django.core.mail.utils import DNS_NAME from django.core.mail.backends.smtp import EmailBackend +from django.core.mail.utils import DNS_NAME class SSLEmailBackend(EmailBackend): diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py index 6ac71df2a..dc136c511 100644 --- a/readthedocs/core/context_processors.py +++ b/readthedocs/core/context_processors.py @@ -1,6 +1,5 @@ """Template context processors for core app.""" -from __future__ import absolute_import from django.conf import settings diff --git a/readthedocs/core/fields.py b/readthedocs/core/fields.py index 87f060908..822ad5ca1 100644 --- a/readthedocs/core/fields.py +++ b/readthedocs/core/fields.py @@ -1,6 +1,5 @@ """Shared model fields and defaults""" -from __future__ import absolute_import import binascii import os diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py index fdc93ffe2..66c09e84b 100644 --- a/readthedocs/core/forms.py +++ b/readthedocs/core/forms.py @@ -1,11 +1,7 @@ # -*- coding: utf-8 -*- """Forms for core app.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging -from builtins import object from django import forms from django.contrib.auth.models import User @@ -14,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from .models import UserProfile + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py index 33a35bc56..3402d1823 100644 --- a/readthedocs/core/management/commands/archive.py +++ b/readthedocs/core/management/commands/archive.py @@ -1,15 +1,14 @@ """Rebuild documentation for all projects""" -from __future__ import absolute_import -from __future__ import print_function -from glob import glob -import os import logging +import os +from glob import glob from django.conf import settings from django.core.management.base import BaseCommand from django.template import loader as template_loader + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py index e47a651cf..1a48748a9 100644 --- a/readthedocs/core/management/commands/clean_builds.py +++ b/readthedocs/core/management/commands/clean_builds.py @@ -1,8 +1,7 @@ """Clean up stable build paths per project version""" -from __future__ import absolute_import -from datetime import timedelta import logging +from datetime import timedelta from optparse import make_option from django.core.management.base import BaseCommand @@ -11,6 +10,7 @@ from django.utils import timezone from readthedocs.builds.models import Build, Version + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py index 3bb34b1b4..2e2e49176 100644 --- a/readthedocs/core/management/commands/import_github.py +++ b/readthedocs/core/management/commands/import_github.py @@ -1,8 +1,7 @@ """Resync GitHub project for user""" -from __future__ import absolute_import -from django.core.management.base import BaseCommand from django.contrib.auth.models import User +from django.core.management.base import BaseCommand from readthedocs.oauth.services import GitHubService diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index f53a80887..1e42685a5 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -8,16 +8,15 @@ Requires a ``GITHUB_AUTH_TOKEN`` to be set in the environment, which should contain a proper GitHub Oauth Token for rate limiting. """ -from __future__ import absolute_import -from __future__ import print_function import os + import requests - -from django.core.management.base import BaseCommand from django.core.cache import cache +from django.core.management.base import BaseCommand -from readthedocs.projects.models import Project from readthedocs.projects.constants import GITHUB_REGEXS, PROGRAMMING_LANGUAGES +from readthedocs.projects.models import Project + PL_DICT = {} diff --git a/readthedocs/core/management/commands/provision_elasticsearch.py b/readthedocs/core/management/commands/provision_elasticsearch.py index 9f29fa37a..690a45803 100644 --- a/readthedocs/core/management/commands/provision_elasticsearch.py +++ b/readthedocs/core/management/commands/provision_elasticsearch.py @@ -1,11 +1,16 @@ """Provision Elastic Search""" -from __future__ import absolute_import import logging from django.core.management.base import BaseCommand -from readthedocs.search.indexes import Index, PageIndex, ProjectIndex, SectionIndex +from readthedocs.search.indexes import ( + Index, + PageIndex, + ProjectIndex, + SectionIndex, +) + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/pull.py b/readthedocs/core/management/commands/pull.py index 3540a35c0..f0ebe4206 100644 --- a/readthedocs/core/management/commands/pull.py +++ b/readthedocs/core/management/commands/pull.py @@ -1,6 +1,5 @@ """Trigger build for project slug""" -from __future__ import absolute_import import logging from django.core.management.base import BaseCommand diff --git a/readthedocs/core/management/commands/reindex_elasticsearch.py b/readthedocs/core/management/commands/reindex_elasticsearch.py index 7a5f25a06..9d2ab8349 100644 --- a/readthedocs/core/management/commands/reindex_elasticsearch.py +++ b/readthedocs/core/management/commands/reindex_elasticsearch.py @@ -1,17 +1,16 @@ """Reindex Elastic Search indexes""" -from __future__ import absolute_import import logging from optparse import make_option -from django.core.management.base import BaseCommand -from django.core.management.base import CommandError from django.conf import settings +from django.core.management.base import BaseCommand, CommandError from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Version from readthedocs.projects.tasks import update_search + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py index dbefcddbd..e8d17a9db 100644 --- a/readthedocs/core/management/commands/set_metadata.py +++ b/readthedocs/core/management/commands/set_metadata.py @@ -1,13 +1,13 @@ """Generate metadata for all projects""" -from __future__ import absolute_import import logging from django.core.management.base import BaseCommand +from readthedocs.core.utils import broadcast from readthedocs.projects import tasks from readthedocs.projects.models import Project -from readthedocs.core.utils import broadcast + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py index e5c039c62..3fda03a41 100644 --- a/readthedocs/core/management/commands/symlink.py +++ b/readthedocs/core/management/commands/symlink.py @@ -1,14 +1,13 @@ """Update symlinks for projects""" -from __future__ import absolute_import import logging from django.core.management.base import BaseCommand from readthedocs.projects import tasks - from readthedocs.projects.models import Project + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/update_api.py b/readthedocs/core/management/commands/update_api.py index e95b23b18..3a9b44b1c 100644 --- a/readthedocs/core/management/commands/update_api.py +++ b/readthedocs/core/management/commands/update_api.py @@ -6,7 +6,6 @@ Usage:: ./manage.py update_api """ -from __future__ import absolute_import import logging from django.core.management.base import BaseCommand diff --git a/readthedocs/core/management/commands/update_repos.py b/readthedocs/core/management/commands/update_repos.py index b5233d2d6..17d095190 100644 --- a/readthedocs/core/management/commands/update_repos.py +++ b/readthedocs/core/management/commands/update_repos.py @@ -6,9 +6,6 @@ Custom management command to rebuild documentation for all projects. Invoked via ``./manage.py update_repos``. """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from django.core.management.base import BaseCommand @@ -18,6 +15,7 @@ from readthedocs.core.utils import trigger_build from readthedocs.projects import tasks from readthedocs.projects.models import Project + log = logging.getLogger(__name__) diff --git a/readthedocs/core/management/commands/update_versions.py b/readthedocs/core/management/commands/update_versions.py index 8961d6706..9cc0c2971 100644 --- a/readthedocs/core/management/commands/update_versions.py +++ b/readthedocs/core/management/commands/update_versions.py @@ -1,6 +1,5 @@ """Rebuild documentation for all projects""" -from __future__ import absolute_import from django.core.management.base import BaseCommand from readthedocs.builds.models import Version diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py index 4556d92ff..967a4e84a 100644 --- a/readthedocs/core/middleware.py +++ b/readthedocs/core/middleware.py @@ -1,22 +1,20 @@ """Middleware for core app.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from django.conf import settings from django.contrib.sessions.middleware import SessionMiddleware from django.core.cache import cache from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist -from django.urls.base import get_urlconf, set_urlconf from django.http import Http404, HttpResponseBadRequest +from django.urls.base import get_urlconf, set_urlconf from django.utils.deprecation import MiddlewareMixin from django.utils.translation import ugettext_lazy as _ from readthedocs.core.utils import cname_to_slug from readthedocs.projects.models import Domain, Project + log = logging.getLogger(__name__) LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]" diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py index 45bf2be75..2460daacd 100644 --- a/readthedocs/core/mixins.py +++ b/readthedocs/core/mixins.py @@ -1,10 +1,8 @@ """Common mixin classes for views""" -from __future__ import absolute_import -from builtins import object -from vanilla import ListView from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator +from vanilla import ListView class ListViewWithForm(ListView): diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index 8b72a7919..6f4f019ae 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- """Models for the core app.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from annoying.fields import AutoOneToOneField from django.db import models -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ugettext from django.urls import reverse +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext +from django.utils.translation import ugettext_lazy as _ + STANDARD_EMAIL = 'anonymous@readthedocs.org' diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py index ad2427b9b..6af5f2a58 100644 --- a/readthedocs/core/permissions.py +++ b/readthedocs/core/permissions.py @@ -1,7 +1,5 @@ """Objects for User permission checks""" -from __future__ import absolute_import - from readthedocs.core.utils.extend import SettingsOverrideObject diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index a17e83216..5e0b27492 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -1,13 +1,11 @@ """URL resolver for documentation.""" -from __future__ import absolute_import -from builtins import object import re from django.conf import settings -from readthedocs.projects.constants import PRIVATE, PUBLIC from readthedocs.core.utils.extend import SettingsOverrideObject +from readthedocs.projects.constants import PRIVATE, PUBLIC class ResolverBase: diff --git a/readthedocs/core/settings.py b/readthedocs/core/settings.py index b758cb57f..5967893cc 100644 --- a/readthedocs/core/settings.py +++ b/readthedocs/core/settings.py @@ -1,7 +1,5 @@ """Class based settings for complex settings inheritance.""" -from __future__ import absolute_import -from builtins import object import inspect import sys diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index d8b90b57d..875cde381 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -2,21 +2,19 @@ """Signal handling for core app.""" -from __future__ import absolute_import - import logging from corsheaders import signals from django.conf import settings +from django.db.models import Count, Q from django.db.models.signals import pre_delete -from django.dispatch import Signal -from django.db.models import Q, Count -from django.dispatch import receiver +from django.dispatch import Signal, receiver from future.backports.urllib.parse import urlparse from rest_framework.permissions import SAFE_METHODS from readthedocs.oauth.models import RemoteOrganization -from readthedocs.projects.models import Project, Domain +from readthedocs.projects.models import Domain, Project + log = logging.getLogger(__name__) diff --git a/readthedocs/core/static.py b/readthedocs/core/static.py index b7ec2b796..71d433b25 100644 --- a/readthedocs/core/static.py +++ b/readthedocs/core/static.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - from django.contrib.staticfiles.finders import FileSystemFinder diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index dcaad60bc..bde0d2d45 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -52,19 +52,11 @@ Example layout fabric -> rtd-builds/fabric/en/latest/ # single version """ -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os import shutil from collections import OrderedDict -from builtins import object from django.conf import settings from readthedocs.builds.models import Version @@ -74,6 +66,7 @@ from readthedocs.doc_builder.environments import LocalEnvironment from readthedocs.projects import constants from readthedocs.projects.models import Domain + log = logging.getLogger(__name__) diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py index 8ed81f1bb..446a19b7e 100644 --- a/readthedocs/core/tasks.py +++ b/readthedocs/core/tasks.py @@ -1,12 +1,11 @@ """Basic tasks.""" -from __future__ import absolute_import import logging from django.conf import settings from django.core.mail import EmailMultiAlternatives -from django.template.loader import get_template from django.template import TemplateDoesNotExist +from django.template.loader import get_template from django.utils import timezone from messages_extends.models import Message as PersistentMessage diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index e91c1fb66..d05831aad 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,10 +1,7 @@ """Template tags for core app.""" -from __future__ import absolute_import - import hashlib -from builtins import str # pylint: disable=redefined-builtin from django import template from django.conf import settings from django.utils.encoding import force_bytes, force_text diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index d18778778..814018dc9 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -1,7 +1,5 @@ """Template tags to query projects by privacy.""" -from __future__ import absolute_import - from django import template from readthedocs.core.permissions import AdminPermission diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py index 6c906c9e8..f96bb30d5 100644 --- a/readthedocs/core/tests/test_signals.py +++ b/readthedocs/core/tests/test_signals.py @@ -1,12 +1,10 @@ -import pytest import django_dynamic_fixture - +import pytest from django.contrib.auth.models import User from readthedocs.oauth.models import RemoteOrganization from readthedocs.projects.models import Project - @pytest.mark.django_db class TestProjectOrganizationSignal: diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py index afc84bda8..b232ef9ae 100644 --- a/readthedocs/core/urls/single_version.py +++ b/readthedocs/core/urls/single_version.py @@ -1,16 +1,15 @@ """URL configuration for a single version.""" -from __future__ import absolute_import - from functools import reduce from operator import add -from django.conf.urls import url from django.conf import settings +from django.conf.urls import url from django.conf.urls.static import static from readthedocs.constants import pattern_opts from readthedocs.core.views import serve + handler500 = 'readthedocs.core.views.server_error_500' handler404 = 'readthedocs.core.views.server_error_404' diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 826c64436..7b5b752fc 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -1,22 +1,19 @@ """URL configurations for subdomains.""" -from __future__ import absolute_import - from functools import reduce from operator import add -from django.conf.urls import url from django.conf import settings +from django.conf.urls import url from django.conf.urls.static import static +from readthedocs.constants import pattern_opts +from readthedocs.core.views import server_error_404, server_error_500 from readthedocs.core.views.serve import ( redirect_page_with_filename, - redirect_project_slug, serve_docs + redirect_project_slug, + serve_docs, ) -from readthedocs.core.views import ( - server_error_500, - server_error_404, -) -from readthedocs.constants import pattern_opts + handler500 = server_error_500 handler404 = server_error_404 diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py index 567c0c23a..c01c76eee 100644 --- a/readthedocs/core/utils/extend.py +++ b/readthedocs/core/utils/extend.py @@ -1,11 +1,9 @@ """Patterns for extending Read the Docs.""" -from __future__ import absolute_import import inspect from django.conf import settings from django.utils.module_loading import import_string -import six def get_override_class(proxy_class, default_class=None): diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py index 1e51fc65b..2c1ccaa9e 100644 --- a/readthedocs/core/utils/tasks/public.py +++ b/readthedocs/core/utils/tasks/public.py @@ -1,17 +1,11 @@ """Celery tasks with publicly viewable status""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from celery import Task, states from django.conf import settings from .retrieve import TaskNotFound, get_task_data + __all__ = ( 'PublicTask', 'TaskNoPermission', 'get_public_task_data' ) diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py index 0aa3e7570..4c5921169 100644 --- a/readthedocs/core/utils/tasks/retrieve.py +++ b/readthedocs/core/utils/tasks/retrieve.py @@ -1,15 +1,9 @@ """Utilities for retrieving task data.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from celery import states from celery.result import AsyncResult + __all__ = ('TaskNotFound', 'get_task_data') diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 3156e3cd4..a92640e12 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,12 +1,5 @@ """Views pertaining to builds.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import json import logging import re @@ -21,6 +14,7 @@ from readthedocs.projects import constants from readthedocs.projects.models import Feature, Project from readthedocs.projects.tasks import sync_repository_task + log = logging.getLogger(__name__) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index d663dc791..7f8bb3651 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -25,18 +25,14 @@ PYTHON_MEDIA (False) - Set this to True to serve docs & media from Python SERVE_DOCS (['private']) - The list of ['private', 'public'] docs to serve. """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging import mimetypes import os from functools import wraps from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import get_object_or_404 -from django.shortcuts import render +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render from django.views.static import serve from readthedocs.builds.models import Version @@ -46,6 +42,7 @@ from readthedocs.core.symlink import PrivateSymlink, PublicSymlink from readthedocs.projects import constants from readthedocs.projects.models import Project, ProjectRelationship + log = logging.getLogger(__name__) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index eeb06379c..65b35f5f2 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -3,9 +3,6 @@ MkDocs_ backend for building docs. .. _MkDocs: http://www.mkdocs.org/ """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import json import logging import os @@ -18,6 +15,7 @@ from readthedocs.doc_builder.base import BaseBuilder from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError from readthedocs.projects.models import Feature + log = logging.getLogger(__name__) diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index 4f2f7b338..8402cfd7e 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -4,27 +4,23 @@ Sphinx_ backend for building docs. .. _Sphinx: http://www.sphinx-doc.org/ """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import codecs -import shutil import logging import os +import shutil import sys import zipfile from glob import glob -import six from django.conf import settings from django.template import loader as template_loader from django.template.loader import render_to_string from readthedocs.builds import utils as version_utils from readthedocs.projects.exceptions import ProjectConfigurationError +from readthedocs.projects.models import Feature from readthedocs.projects.utils import safe_write from readthedocs.restapi.client import api -from readthedocs.projects.models import Feature from ..base import BaseBuilder, restoring_chdir from ..constants import PDF_RE @@ -32,6 +28,7 @@ from ..environments import BuildCommand, DockerBuildCommand from ..exceptions import BuildEnvironmentError from ..signals import finalize_sphinx_context_data + log = logging.getLogger(__name__) diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py index c382fefc3..8810ab5f7 100644 --- a/readthedocs/doc_builder/base.py +++ b/readthedocs/doc_builder/base.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- """Base classes for Builders.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging import os import shutil -from builtins import object from functools import wraps + log = logging.getLogger(__name__) diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py index 00a0095bc..9eabeaa0c 100644 --- a/readthedocs/doc_builder/config.py +++ b/readthedocs/doc_builder/config.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- """An API to load config from a readthedocs.yml file.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - from os import path from readthedocs.config import BuildConfigV1, ConfigError, InvalidConfig diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py index 1cd1a5b13..f2f15010b 100644 --- a/readthedocs/doc_builder/constants.py +++ b/readthedocs/doc_builder/constants.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- """Doc build constants.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging import os import re from django.conf import settings + log = logging.getLogger(__name__) MKDOCS_TEMPLATE_DIR = os.path.join( diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index 10ba06f5c..cbdff522d 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -2,13 +2,6 @@ """Documentation Builder Environments.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os import re @@ -18,8 +11,6 @@ import sys import traceback from datetime import datetime -import six -from builtins import object, str from django.conf import settings from django.utils.translation import ugettext_lazy as _ from docker import APIClient diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 80582595b..5898dfce0 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """Exceptions raised when building documentation.""" -from __future__ import division, print_function, unicode_literals - from django.utils.translation import ugettext_noop diff --git a/readthedocs/doc_builder/loader.py b/readthedocs/doc_builder/loader.py index 0edcaace7..8d733b3d8 100644 --- a/readthedocs/doc_builder/loader.py +++ b/readthedocs/doc_builder/loader.py @@ -1,9 +1,9 @@ """Lookup tables for builders and backends.""" -from __future__ import absolute_import from importlib import import_module from django.conf import settings + # Managers mkdocs = import_module( getattr(settings, 'MKDOCS_BACKEND', diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index 7d85a275f..92ec9a2f5 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """An abstraction over virtualenv and Conda environments.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import copy import itertools import json @@ -15,8 +8,6 @@ import logging import os import shutil -import six -from builtins import object, open from django.conf import settings from readthedocs.doc_builder.config import load_yaml_config @@ -26,6 +17,7 @@ from readthedocs.doc_builder.loader import get_builder_class from readthedocs.projects.constants import LOG_TEMPLATE from readthedocs.projects.models import Feature + log = logging.getLogger(__name__) diff --git a/readthedocs/doc_builder/signals.py b/readthedocs/doc_builder/signals.py index 419531a56..08991fabb 100644 --- a/readthedocs/doc_builder/signals.py +++ b/readthedocs/doc_builder/signals.py @@ -1,9 +1,8 @@ """Signals for adding custom context data""" -from __future__ import absolute_import - import django.dispatch + finalize_sphinx_context_data = django.dispatch.Signal( providing_args=['buildenv', 'context', 'response_data'] ) diff --git a/readthedocs/gold/admin.py b/readthedocs/gold/admin.py index ecd512b75..a1dc3e80e 100644 --- a/readthedocs/gold/admin.py +++ b/readthedocs/gold/admin.py @@ -1,7 +1,7 @@ """Django admin configuration for the Gold Membership app.""" -from __future__ import absolute_import from django.contrib import admin + from .models import GoldUser diff --git a/readthedocs/gold/apps.py b/readthedocs/gold/apps.py index eda30768d..380a21f09 100644 --- a/readthedocs/gold/apps.py +++ b/readthedocs/gold/apps.py @@ -1,6 +1,5 @@ """Django app configuration for the Gold Membership app.""" -from __future__ import absolute_import from django.apps import AppConfig diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index fead5e26b..31c4a18aa 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -1,10 +1,6 @@ """Gold subscription forms.""" -from __future__ import absolute_import - -from builtins import object from django import forms - from django.utils.translation import ugettext_lazy as _ from readthedocs.payments.forms import StripeModelForm, StripeResourceMixin diff --git a/readthedocs/gold/models.py b/readthedocs/gold/models.py index 278dc6022..c73ec79e3 100644 --- a/readthedocs/gold/models.py +++ b/readthedocs/gold/models.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Django models for recurring donations aka Gold Membership.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import math from django.db import models @@ -16,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from readthedocs.projects.models import Project + #: The membership options that are currently available LEVEL_CHOICES = ( ('v1-org-5', '$5/mo'), diff --git a/readthedocs/gold/signals.py b/readthedocs/gold/signals.py index a3cef14ca..4c089ebb0 100644 --- a/readthedocs/gold/signals.py +++ b/readthedocs/gold/signals.py @@ -1,6 +1,5 @@ """Gold model signals""" -from __future__ import absolute_import from django.db.models.signals import pre_delete from django.dispatch import receiver diff --git a/readthedocs/gold/tests/test_forms.py b/readthedocs/gold/tests/test_forms.py index acf1c82fe..5ffaf9183 100644 --- a/readthedocs/gold/tests/test_forms.py +++ b/readthedocs/gold/tests/test_forms.py @@ -1,14 +1,12 @@ -from __future__ import absolute_import -import mock import django_dynamic_fixture as fixture -from django.test import TestCase +import mock from django.contrib.auth.models import User +from django.test import TestCase from readthedocs.projects.models import Project -from ..models import GoldUser from ..forms import GoldSubscriptionForm - +from ..models import GoldUser class GoldSubscriptionFormTests(TestCase): diff --git a/readthedocs/gold/tests/test_signals.py b/readthedocs/gold/tests/test_signals.py index 62618581c..947f042e0 100644 --- a/readthedocs/gold/tests/test_signals.py +++ b/readthedocs/gold/tests/test_signals.py @@ -1,16 +1,14 @@ -from __future__ import absolute_import -import mock import django_dynamic_fixture as fixture -from django.test import TestCase +import mock from django.contrib.auth.models import User from django.db.models.signals import pre_delete +from django.test import TestCase from readthedocs.projects.models import Project from ..models import GoldUser from ..signals import delete_customer - class GoldSignalTests(TestCase): def setUp(self): diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py index dd8b6e354..2a1e1acaf 100644 --- a/readthedocs/gold/urls.py +++ b/readthedocs/gold/urls.py @@ -1,6 +1,5 @@ """Gold subscription URLs""" -from __future__ import absolute_import from django.conf.urls import url from readthedocs.gold import views diff --git a/readthedocs/gold/views.py b/readthedocs/gold/views.py index 7e2677e2a..2867d49db 100644 --- a/readthedocs/gold/views.py +++ b/readthedocs/gold/views.py @@ -1,20 +1,13 @@ # -*- coding: utf-8 -*- """Gold subscription views.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals -) - from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.messages.views import SuccessMessageMixin -from django.urls import reverse, reverse_lazy from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render +from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from vanilla import DeleteView, DetailView, UpdateView diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py index 5fabe23be..24f0ff951 100644 --- a/readthedocs/integrations/admin.py +++ b/readthedocs/integrations/admin.py @@ -1,12 +1,11 @@ """Integration admin models.""" -from __future__ import absolute_import -from django.contrib import admin from django import urls +from django.contrib import admin from django.utils.safestring import mark_safe from pygments.formatters import HtmlFormatter -from .models import Integration, HttpExchange +from .models import HttpExchange, Integration def pretty_json_field(field, description, include_styles=False): diff --git a/readthedocs/integrations/models.py b/readthedocs/integrations/models.py index 032c16c28..8dbbbf80e 100644 --- a/readthedocs/integrations/models.py +++ b/readthedocs/integrations/models.py @@ -2,18 +2,10 @@ """Integration models for external services.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import json import re import uuid -from builtins import object, str from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation, diff --git a/readthedocs/notifications/apps.py b/readthedocs/notifications/apps.py index 38ed93cda..3be3e557b 100644 --- a/readthedocs/notifications/apps.py +++ b/readthedocs/notifications/apps.py @@ -1,5 +1,4 @@ """Django app configuration for the notifications app.""" -from __future__ import absolute_import from django.apps import AppConfig diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py index e2552c1bd..e2a3ed8df 100644 --- a/readthedocs/notifications/backends.py +++ b/readthedocs/notifications/backends.py @@ -7,10 +7,6 @@ Django settings. For example, they might be e-mailed to users as well as displayed on the site. """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - -from builtins import object from django.conf import settings from django.http import HttpRequest from django.utils.module_loading import import_string diff --git a/readthedocs/notifications/constants.py b/readthedocs/notifications/constants.py index d15efa984..4ee285d17 100644 --- a/readthedocs/notifications/constants.py +++ b/readthedocs/notifications/constants.py @@ -1,6 +1,5 @@ """Notification constants""" -from __future__ import absolute_import from messages_extends import constants as message_constants diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py index 589243085..f0026d5b3 100644 --- a/readthedocs/notifications/forms.py +++ b/readthedocs/notifications/forms.py @@ -1,5 +1,4 @@ """HTML forms for sending notifications.""" -from __future__ import absolute_import from django import forms from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py index 8329bb91b..e363bc16a 100644 --- a/readthedocs/notifications/notification.py +++ b/readthedocs/notifications/notification.py @@ -1,17 +1,16 @@ # -*- coding: utf-8 -*- """Support for templating of notifications.""" -from __future__ import absolute_import -from builtins import object import logging + from django.conf import settings -from django.template import Template, Context -from django.template.loader import render_to_string from django.db import models from django.http import HttpRequest +from django.template import Context, Template +from django.template.loader import render_to_string -from .backends import send_notification from . import constants +from .backends import send_notification log = logging.getLogger(__name__) diff --git a/readthedocs/notifications/storages.py b/readthedocs/notifications/storages.py index 9a98ef763..ccd9f8e9a 100644 --- a/readthedocs/notifications/storages.py +++ b/readthedocs/notifications/storages.py @@ -1,20 +1,21 @@ # -*- coding: utf-8 -*- """Customised storage for notifications.""" -from __future__ import absolute_import from django.contrib.messages.storage.base import Message from django.db.models import Q from django.utils.safestring import mark_safe -from messages_extends.storages import FallbackStorage, PersistentStorage -from messages_extends.models import Message as PersistentMessage from messages_extends.constants import PERSISTENT_MESSAGE_LEVELS +from messages_extends.models import Message as PersistentMessage +from messages_extends.storages import FallbackStorage, PersistentStorage + +from .constants import NON_PERSISTENT_MESSAGE_LEVELS + try: from django.utils import timezone except ImportError: from datetime import datetime as timezone -from .constants import NON_PERSISTENT_MESSAGE_LEVELS class FallbackUniqueStorage(FallbackStorage): diff --git a/readthedocs/notifications/urls.py b/readthedocs/notifications/urls.py index 349c1f293..d44522bc9 100644 --- a/readthedocs/notifications/urls.py +++ b/readthedocs/notifications/urls.py @@ -1,9 +1,7 @@ """Renames for messages_extends URLs""" -from __future__ import absolute_import from django.conf.urls import url - -from messages_extends.views import message_mark_read, message_mark_all_read +from messages_extends.views import message_mark_all_read, message_mark_read urlpatterns = [ diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py index 2cbdb0959..3853e8cdb 100644 --- a/readthedocs/notifications/views.py +++ b/readthedocs/notifications/views.py @@ -1,8 +1,7 @@ """Django views for the notifications app.""" -from __future__ import absolute_import -from django.views.generic import FormView from django.contrib import admin, messages from django.http import HttpResponseRedirect +from django.views.generic import FormView from .forms import SendNotificationForm diff --git a/readthedocs/oauth/admin.py b/readthedocs/oauth/admin.py index b961e6472..777e48272 100644 --- a/readthedocs/oauth/admin.py +++ b/readthedocs/oauth/admin.py @@ -1,9 +1,8 @@ """Admin configuration for the OAuth app.""" -from __future__ import absolute_import from django.contrib import admin -from .models import RemoteRepository, RemoteOrganization +from .models import RemoteOrganization, RemoteRepository class RemoteRepositoryAdmin(admin.ModelAdmin): diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py index 54a02191f..7d6aeec15 100644 --- a/readthedocs/oauth/models.py +++ b/readthedocs/oauth/models.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- """OAuth service models.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import json -from builtins import object from allauth.socialaccount.models import SocialAccount from django.conf import settings from django.contrib.auth.models import User -from django.urls import reverse from django.core.validators import URLValidator from django.db import models from django.db.models import Q +from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py index 41b9c78ce..915a2003c 100644 --- a/readthedocs/oauth/notifications.py +++ b/readthedocs/oauth/notifications.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from messages_extends.constants import ERROR_PERSISTENT diff --git a/readthedocs/oauth/querysets.py b/readthedocs/oauth/querysets.py index d01703eb1..c23f6cbee 100644 --- a/readthedocs/oauth/querysets.py +++ b/readthedocs/oauth/querysets.py @@ -1,7 +1,5 @@ """Managers for OAuth models""" -from __future__ import absolute_import - from django.db import models from readthedocs.core.utils.extend import SettingsOverrideObject diff --git a/readthedocs/oauth/services/base.py b/readthedocs/oauth/services/base.py index 87086a031..77f2a7bf1 100644 --- a/readthedocs/oauth/services/base.py +++ b/readthedocs/oauth/services/base.py @@ -1,21 +1,18 @@ # -*- coding: utf-8 -*- """OAuth utility functions.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from datetime import datetime from allauth.socialaccount.models import SocialAccount from allauth.socialaccount.providers import registry -from builtins import object from django.conf import settings from django.utils import timezone from oauthlib.oauth2.rfc6749.errors import InvalidClientIdError from requests.exceptions import RequestException from requests_oauthlib import OAuth2Session + log = logging.getLogger(__name__) diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py index 2586de569..70fb90b0b 100644 --- a/readthedocs/oauth/services/bitbucket.py +++ b/readthedocs/oauth/services/bitbucket.py @@ -1,16 +1,15 @@ """OAuth utility functions.""" -from __future__ import absolute_import -from builtins import str -import logging import json +import logging import re +from allauth.socialaccount.providers.bitbucket_oauth2.views import ( + BitbucketOAuth2Adapter, +) from django.conf import settings from django.urls import reverse from requests.exceptions import RequestException -from allauth.socialaccount.providers.bitbucket_oauth2.views import ( - BitbucketOAuth2Adapter) from readthedocs.builds import utils as build_utils from readthedocs.integrations.models import Integration diff --git a/readthedocs/oauth/services/github.py b/readthedocs/oauth/services/github.py index f9a03c4ff..2e844aaaf 100644 --- a/readthedocs/oauth/services/github.py +++ b/readthedocs/oauth/services/github.py @@ -1,16 +1,14 @@ """OAuth utility functions.""" -from __future__ import absolute_import -from builtins import str -import logging import json +import logging import re +from allauth.socialaccount.models import SocialToken +from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from django.conf import settings from django.urls import reverse from requests.exceptions import RequestException -from allauth.socialaccount.models import SocialToken -from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from readthedocs.builds import utils as build_utils from readthedocs.integrations.models import Integration @@ -19,6 +17,7 @@ from readthedocs.restapi.client import api from ..models import RemoteOrganization, RemoteRepository from .base import Service + log = logging.getLogger(__name__) diff --git a/readthedocs/oauth/services/gitlab.py b/readthedocs/oauth/services/gitlab.py index b9562617a..ada8a6f1d 100644 --- a/readthedocs/oauth/services/gitlab.py +++ b/readthedocs/oauth/services/gitlab.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """OAuth utility functions.""" -from __future__ import division, print_function, unicode_literals - import json import logging import re @@ -19,6 +17,7 @@ from readthedocs.projects.models import Project from ..models import RemoteOrganization, RemoteRepository from .base import Service + try: from urlparse import urljoin, urlparse except ImportError: diff --git a/readthedocs/oauth/tasks.py b/readthedocs/oauth/tasks.py index 45b49ceac..eee0efe48 100644 --- a/readthedocs/oauth/tasks.py +++ b/readthedocs/oauth/tasks.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Tasks for OAuth services.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging from allauth.socialaccount.providers import registry as allauth_registry @@ -23,6 +16,7 @@ from readthedocs.worker import app from .services import registry + log = logging.getLogger(__name__) diff --git a/readthedocs/oauth/utils.py b/readthedocs/oauth/utils.py index 64e7dc7ed..d797baa05 100644 --- a/readthedocs/oauth/utils.py +++ b/readthedocs/oauth/utils.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- """Support code for OAuth, including webhook support.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from django.contrib import messages @@ -11,9 +8,14 @@ from django.utils.translation import ugettext_lazy as _ from readthedocs.integrations.models import Integration from readthedocs.oauth.services import ( - BitbucketService, GitHubService, GitLabService, registry) + BitbucketService, + GitHubService, + GitLabService, + registry, +) from readthedocs.projects.models import Project + log = logging.getLogger(__name__) SERVICE_MAP = { diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py index 89b2c1dc7..c4501d682 100644 --- a/readthedocs/payments/forms.py +++ b/readthedocs/payments/forms.py @@ -1,17 +1,15 @@ """Payment forms.""" -from __future__ import absolute_import -from builtins import str -from builtins import object import logging -from stripe import Customer, Charge -from stripe.error import InvalidRequestError from django import forms from django.utils.translation import ugettext_lazy as _ +from stripe import Charge, Customer +from stripe.error import InvalidRequestError from .utils import stripe + log = logging.getLogger(__name__) diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py index d90fc1699..374114786 100644 --- a/readthedocs/payments/mixins.py +++ b/readthedocs/payments/mixins.py @@ -1,7 +1,5 @@ """Payment view mixin classes.""" -from __future__ import absolute_import -from builtins import object from django.conf import settings diff --git a/readthedocs/payments/utils.py b/readthedocs/payments/utils.py index a65b5b0a8..36dc1f769 100644 --- a/readthedocs/payments/utils.py +++ b/readthedocs/payments/utils.py @@ -5,10 +5,10 @@ These are mostly one-off functions. Define the bulk of Stripe operations on :py:class:`readthedocs.payments.forms.StripeResourceMixin`. """ -from __future__ import absolute_import import stripe from django.conf import settings + stripe.api_key = getattr(settings, 'STRIPE_SECRET', None) diff --git a/readthedocs/profiles/urls/private.py b/readthedocs/profiles/urls/private.py index 85acbf2d9..21a6e604f 100644 --- a/readthedocs/profiles/urls/private.py +++ b/readthedocs/profiles/urls/private.py @@ -1,12 +1,5 @@ """URL patterns for views to modify user profiles.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.conf.urls import url from readthedocs.core.forms import UserProfileForm diff --git a/readthedocs/profiles/urls/public.py b/readthedocs/profiles/urls/public.py index 2a9c458e6..0cade8013 100644 --- a/readthedocs/profiles/urls/public.py +++ b/readthedocs/profiles/urls/public.py @@ -1,6 +1,5 @@ """URL patterns to view user profiles.""" -from __future__ import absolute_import from django.conf.urls import url from readthedocs.profiles import views diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index d288f4767..0396795e9 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -1,20 +1,13 @@ # -*- coding: utf-8 -*- """Views for creating, editing and viewing site-specific user profiles.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.contrib import messages from django.contrib.auth import logout from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.urls import reverse from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from readthedocs.core.forms import UserAdvertisingForm, UserDeleteForm diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 48109f7ea..5beebc58d 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Django administration interface for `projects.models`""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.contrib import admin, messages from django.contrib.admin.actions import delete_selected from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/projects/backends/views.py b/readthedocs/projects/backends/views.py index 2b3d0fa41..428ca3f82 100644 --- a/readthedocs/projects/backends/views.py +++ b/readthedocs/projects/backends/views.py @@ -5,7 +5,6 @@ Use these views instead of calling the views directly, in order to allow for settings override of the view class. """ -from __future__ import absolute_import from readthedocs.core.utils.extend import SettingsOverrideObject from readthedocs.projects.views import private diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index 7e6d130e9..f357942d6 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -6,13 +6,11 @@ Default values and other various configuration for projects, including available theme names and repository types. """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import re from django.utils.translation import ugettext_lazy as _ + DOCUMENTATION_CHOICES = ( ('auto', _('Automatically Choose')), ('sphinx', _('Sphinx Html')), diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 41daacdea..07ccd5624 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """Project exceptions.""" -from __future__ import division, print_function, unicode_literals - from django.conf import settings from django.utils.translation import ugettext_noop as _ diff --git a/readthedocs/projects/feeds.py b/readthedocs/projects/feeds.py index b3739f4b0..de3cc008b 100644 --- a/readthedocs/projects/feeds.py +++ b/readthedocs/projects/feeds.py @@ -1,6 +1,5 @@ """Project RSS feeds""" -from __future__ import absolute_import from django.contrib.syndication.views import Feed from readthedocs.projects.models import Project diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index c447ab40c..a92921388 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -1,16 +1,8 @@ # -*- coding: utf-8 -*- """Project forms.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from random import choice -from builtins import object from django import forms from django.conf import settings from django.contrib.auth.models import User diff --git a/readthedocs/projects/management/commands/import_project_from_live.py b/readthedocs/projects/management/commands/import_project_from_live.py index 95c857783..042caf709 100644 --- a/readthedocs/projects/management/commands/import_project_from_live.py +++ b/readthedocs/projects/management/commands/import_project_from_live.py @@ -1,12 +1,12 @@ """Import project command""" -from __future__ import absolute_import +import json + +import slumber +from django.contrib.auth.models import User from django.core.management import call_command from django.core.management.base import BaseCommand -import json -import slumber -from django.contrib.auth.models import User from ...models import Project diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index c87295b86..24bda8f18 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1,18 +1,14 @@ # -*- coding: utf-8 -*- """Project models.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import fnmatch import logging import os -from builtins import object # pylint: disable=redefined-builtin from django.conf import settings from django.contrib.auth.models import User -from django.urls import NoReverseMatch, reverse from django.db import models +from django.urls import NoReverseMatch, reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django_extensions.db.models import TimeStampedModel @@ -26,15 +22,22 @@ from readthedocs.core.utils import broadcast, slugify from readthedocs.projects import constants from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.querysets import ( - ChildRelatedProjectQuerySet, FeatureQuerySet, ProjectQuerySet, - RelatedProjectQuerySet) + ChildRelatedProjectQuerySet, + FeatureQuerySet, + ProjectQuerySet, + RelatedProjectQuerySet, +) from readthedocs.projects.templatetags.projects_tags import sort_version_aware -from readthedocs.projects.validators import validate_domain_name, validate_repository_url +from readthedocs.projects.validators import ( + validate_domain_name, + validate_repository_url, +) from readthedocs.projects.version_handling import determine_stable_version from readthedocs.restapi.client import api from readthedocs.vcs_support.backends import backend_cls from readthedocs.vcs_support.utils import Lock, NonBlockingLock + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index eafbecf66..fc46cb0fd 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -1,6 +1,5 @@ """Project notifications""" -from __future__ import absolute_import from readthedocs.notifications import Notification from readthedocs.notifications.constants import REQUIREMENT diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py index 3884eb26d..4bde40fa3 100644 --- a/readthedocs/projects/querysets.py +++ b/readthedocs/projects/querysets.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """Project model QuerySet classes.""" -from __future__ import absolute_import - from django.db import models from django.db.models import Q from guardian.shortcuts import get_objects_for_user diff --git a/readthedocs/projects/signals.py b/readthedocs/projects/signals.py index 6ef49f9e6..61251c329 100644 --- a/readthedocs/projects/signals.py +++ b/readthedocs/projects/signals.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Project signals""" -from __future__ import absolute_import import django.dispatch diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 10b6660de..06ec7981e 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -6,13 +6,6 @@ This includes fetching repository code, cleaning ``conf.py`` files, and rebuilding documentation. """ -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import datetime import hashlib import json @@ -23,11 +16,10 @@ import socket from collections import Counter, defaultdict import requests -from builtins import str from celery.exceptions import SoftTimeLimitExceeded from django.conf import settings -from django.urls import reverse from django.db.models import Q +from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from slumber.exceptions import HttpClientError @@ -56,11 +48,11 @@ from readthedocs.doc_builder.environments import ( ) from readthedocs.doc_builder.exceptions import ( BuildEnvironmentError, + BuildEnvironmentWarning, BuildTimeoutError, ProjectBuildsSkippedError, VersionLockedError, YAMLParseError, - BuildEnvironmentWarning, ) from readthedocs.doc_builder.loader import get_builder_class from readthedocs.doc_builder.python_environments import Conda, Virtualenv @@ -82,6 +74,7 @@ from .signals import ( files_changed, ) + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/templatetags/projects_tags.py b/readthedocs/projects/templatetags/projects_tags.py index 699b1a249..34c975085 100644 --- a/readthedocs/projects/templatetags/projects_tags.py +++ b/readthedocs/projects/templatetags/projects_tags.py @@ -1,6 +1,5 @@ """Project template tags and filters""" -from __future__ import absolute_import from django import template from readthedocs.projects.version_handling import comparable_version diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index 1cdc29441..a5ca6388f 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -1,17 +1,27 @@ """Project URLs for authenticated users""" -from __future__ import absolute_import from django.conf.urls import url from readthedocs.constants import pattern_opts +from readthedocs.projects.backends.views import ImportDemoView, ImportWizardView from readthedocs.projects.views import private from readthedocs.projects.views.private import ( - ProjectDashboard, ImportView, - ProjectUpdate, ProjectAdvancedUpdate, - DomainList, DomainCreate, DomainDelete, DomainUpdate, - IntegrationList, IntegrationCreate, IntegrationDetail, IntegrationDelete, - IntegrationExchangeDetail, IntegrationWebhookSync, ProjectAdvertisingUpdate) -from readthedocs.projects.backends.views import ImportWizardView, ImportDemoView + DomainCreate, + DomainDelete, + DomainList, + DomainUpdate, + ImportView, + IntegrationCreate, + IntegrationDelete, + IntegrationDetail, + IntegrationExchangeDetail, + IntegrationList, + IntegrationWebhookSync, + ProjectAdvancedUpdate, + ProjectAdvertisingUpdate, + ProjectDashboard, + ProjectUpdate, +) urlpatterns = [ diff --git a/readthedocs/projects/urls/public.py b/readthedocs/projects/urls/public.py index b46b8105a..be0d0fc17 100644 --- a/readthedocs/projects/urls/public.py +++ b/readthedocs/projects/urls/public.py @@ -1,13 +1,11 @@ """Project URLS for public users""" -from __future__ import absolute_import from django.conf.urls import url -from readthedocs.projects.views import public -from readthedocs.projects.views.public import ProjectIndex, ProjectDetailView - from readthedocs.builds import views as build_views from readthedocs.constants import pattern_opts +from readthedocs.projects.views import public +from readthedocs.projects.views.public import ProjectDetailView, ProjectIndex urlpatterns = [ diff --git a/readthedocs/projects/utils.py b/readthedocs/projects/utils.py index 840dd1748..cdacd26c2 100644 --- a/readthedocs/projects/utils.py +++ b/readthedocs/projects/utils.py @@ -1,19 +1,12 @@ # -*- coding: utf-8 -*- """Utility functions used by projects.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os -from builtins import open from django.conf import settings + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py index f2b7b4687..a362692c4 100644 --- a/readthedocs/projects/validators.py +++ b/readthedocs/projects/validators.py @@ -1,14 +1,12 @@ """Validators for projects app.""" -# From https://github.com/django/django/pull/3477/files -from __future__ import absolute_import import re from django.conf import settings from django.core.exceptions import ValidationError +from django.core.validators import RegexValidator from django.utils.deconstruct import deconstructible from django.utils.translation import ugettext_lazy as _ -from django.core.validators import RegexValidator from future.backports.urllib.parse import urlparse diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index d3ef3d3a1..d1ef1751a 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- """Project version handling.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import unicodedata -import six from packaging.version import InvalidVersion, Version from readthedocs.builds.constants import ( - LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME, TAG) + LATEST_VERBOSE_NAME, + STABLE_VERBOSE_NAME, + TAG, +) def parse_version_failsafe(version_string): diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py index b6e8e387d..fa85dd440 100644 --- a/readthedocs/projects/views/base.py +++ b/readthedocs/projects/views/base.py @@ -1,21 +1,18 @@ # -*- coding: utf-8 -*- """Mix-in classes for project views.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging -from builtins import object from datetime import timedelta from django.conf import settings -from django.urls import reverse from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.urls import reverse from django.utils import timezone from ..exceptions import ProjectSpamError from ..models import Project + log = logging.getLogger(__name__) USER_MATURITY_DAYS = getattr(settings, 'USER_MATURITY_DAYS', 7) diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py index 1692b0e46..ed04d0377 100644 --- a/readthedocs/projects/views/mixins.py +++ b/readthedocs/projects/views/mixins.py @@ -1,7 +1,5 @@ """Mixin classes for project views.""" -from __future__ import absolute_import -from builtins import object from django.shortcuts import get_object_or_404 from readthedocs.projects.models import Project diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 22b355f94..7a3e8b752 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -2,13 +2,6 @@ """Project views for authenticated users.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging from allauth.socialaccount.models import SocialAccount @@ -66,6 +59,7 @@ from readthedocs.projects.models import ( from readthedocs.projects.signals import project_import from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py index 028571a29..87a76947d 100644 --- a/readthedocs/projects/views/public.py +++ b/readthedocs/projects/views/public.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- """Public project views.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import json import logging import mimetypes @@ -16,9 +13,9 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.models import User from django.core.cache import cache -from django.urls import reverse from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render +from django.urls import reverse from django.views.decorators.cache import never_cache from django.views.generic import DetailView, ListView from taggit.models import Tag @@ -32,6 +29,7 @@ from readthedocs.search.views import LOG_TEMPLATE from .base import ProjectOnboardMixin + log = logging.getLogger(__name__) search_log = logging.getLogger(__name__ + '.search') mimetypes.add_type('application/epub+zip', '.epub') diff --git a/readthedocs/redirects/admin.py b/readthedocs/redirects/admin.py index 6bd2d7347..2091ab87a 100644 --- a/readthedocs/redirects/admin.py +++ b/readthedocs/redirects/admin.py @@ -1,8 +1,7 @@ """Django admin configuration for the redirects app.""" -from __future__ import absolute_import - from django.contrib import admin + from .models import Redirect diff --git a/readthedocs/redirects/managers.py b/readthedocs/redirects/managers.py index 9c0f1bf47..af18ebae6 100644 --- a/readthedocs/redirects/managers.py +++ b/readthedocs/redirects/managers.py @@ -1,6 +1,5 @@ """Manager and queryset for the redirects app.""" -from __future__ import absolute_import from django.db.models import Manager from django.db.models.query import QuerySet diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py index f8eddb16c..a4e9b72f7 100644 --- a/readthedocs/redirects/models.py +++ b/readthedocs/redirects/models.py @@ -1,16 +1,16 @@ """Django models for the redirects app.""" -from __future__ import absolute_import -from builtins import object +import logging +import re + from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ -import logging -import re from readthedocs.core.resolver import resolve_path from readthedocs.projects.models import Project + from .managers import RedirectManager diff --git a/readthedocs/redirects/utils.py b/readthedocs/redirects/utils.py index 1edc62862..0817d99b1 100644 --- a/readthedocs/redirects/utils.py +++ b/readthedocs/redirects/utils.py @@ -7,11 +7,11 @@ with it in the database, and generating a redirect response. These are not used directly as views; they are instead included into 404 handlers, so that redirects only take effect if no other view matches. """ -from __future__ import absolute_import -from django.http import HttpResponseRedirect import logging import re +from django.http import HttpResponseRedirect + from readthedocs.constants import LANGUAGES_REGEX from readthedocs.projects.models import Project diff --git a/readthedocs/restapi/client.py b/readthedocs/restapi/client.py index 83f5b861d..a2f9943c6 100644 --- a/readthedocs/restapi/client.py +++ b/readthedocs/restapi/client.py @@ -2,13 +2,6 @@ """Simple client to access our API with Slumber credentials.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import requests @@ -18,6 +11,7 @@ from requests_toolbelt.adapters import host_header_ssl from rest_framework.renderers import JSONRenderer from slumber import API, serialize + log = logging.getLogger(__name__) PRODUCTION_DOMAIN = getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org') diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py index a9d7aa8d9..4645cca61 100644 --- a/readthedocs/restapi/permissions.py +++ b/readthedocs/restapi/permissions.py @@ -1,7 +1,5 @@ """Defines access permissions for the API.""" -from __future__ import absolute_import - from rest_framework import permissions from readthedocs.core.permissions import AdminPermission diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py index ba6a88ddc..4529bcdaf 100644 --- a/readthedocs/restapi/serializers.py +++ b/readthedocs/restapi/serializers.py @@ -1,15 +1,11 @@ """Defines serializers for each of our models.""" -from __future__ import absolute_import - -from builtins import object - from allauth.socialaccount.models import SocialAccount from rest_framework import serializers from readthedocs.builds.models import Build, BuildCommandResult, Version from readthedocs.oauth.models import RemoteOrganization, RemoteRepository -from readthedocs.projects.models import Project, Domain +from readthedocs.projects.models import Domain, Project class ProjectSerializer(serializers.ModelSerializer): diff --git a/readthedocs/restapi/signals.py b/readthedocs/restapi/signals.py index 6b6d0b395..6968cb534 100644 --- a/readthedocs/restapi/signals.py +++ b/readthedocs/restapi/signals.py @@ -1,9 +1,8 @@ """We define custom Django signals to trigger when a footer is rendered.""" -from __future__ import absolute_import - import django.dispatch + footer_response = django.dispatch.Signal( providing_args=["request", "context", "response_data"] ) diff --git a/readthedocs/restapi/urls.py b/readthedocs/restapi/urls.py index c8cdf6cd2..50767355f 100644 --- a/readthedocs/restapi/urls.py +++ b/readthedocs/restapi/urls.py @@ -2,13 +2,6 @@ """Define routes between URL paths and views/endpoints.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.conf import settings from django.conf.urls import include, url from rest_framework import routers @@ -35,6 +28,7 @@ from .views.model_views import ( VersionViewSet, ) + router = routers.DefaultRouter() router.register(r'build', BuildViewSet, basename='build') router.register(r'command', BuildCommandViewSet, basename='buildcommandresult') diff --git a/readthedocs/restapi/utils.py b/readthedocs/restapi/utils.py index 8637cd177..19a325be5 100644 --- a/readthedocs/restapi/utils.py +++ b/readthedocs/restapi/utils.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Utility functions that are used by both views and celery tasks.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import hashlib import logging @@ -25,6 +18,7 @@ from readthedocs.builds.constants import ( from readthedocs.builds.models import Version from readthedocs.search.indexes import PageIndex, ProjectIndex, SectionIndex + log = logging.getLogger(__name__) diff --git a/readthedocs/restapi/views/core_views.py b/readthedocs/restapi/views/core_views.py index 08fc9e7d7..e6f2879b2 100644 --- a/readthedocs/restapi/views/core_views.py +++ b/readthedocs/restapi/views/core_views.py @@ -1,17 +1,14 @@ """Utility endpoints relating to canonical urls, embedded content, etc.""" -from __future__ import absolute_import - +from django.shortcuts import get_object_or_404 from rest_framework import decorators, permissions, status from rest_framework.renderers import JSONRenderer from rest_framework.response import Response -from django.shortcuts import get_object_or_404 - from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Version -from readthedocs.projects.models import Project from readthedocs.core.templatetags.core_tags import make_document_url +from readthedocs.projects.models import Project @decorators.api_view(['GET']) diff --git a/readthedocs/restapi/views/footer_views.py b/readthedocs/restapi/views/footer_views.py index ad5ec7028..2f0bcae0e 100644 --- a/readthedocs/restapi/views/footer_views.py +++ b/readthedocs/restapi/views/footer_views.py @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- """Endpoint to generate footer HTML.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - -import six from django.conf import settings from django.shortcuts import get_object_or_404 from django.template import loader as template_loader @@ -17,7 +13,9 @@ from readthedocs.builds.constants import LATEST, TAG from readthedocs.builds.models import Version from readthedocs.projects.models import Project from readthedocs.projects.version_handling import ( - highest_version, parse_version_failsafe) + highest_version, + parse_version_failsafe, +) from readthedocs.restapi.signals import footer_response diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py index 495051840..b8320c352 100644 --- a/readthedocs/restapi/views/integrations.py +++ b/readthedocs/restapi/views/integrations.py @@ -1,18 +1,10 @@ # -*- coding: utf-8 -*- """Endpoints integrating with Github, Bitbucket, and other webhooks.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import json import logging import re -import six from django.shortcuts import get_object_or_404 from rest_framework import permissions, status from rest_framework.exceptions import NotFound, ParseError @@ -30,6 +22,7 @@ from readthedocs.integrations.models import HttpExchange, Integration from readthedocs.integrations.utils import normalize_request_payload from readthedocs.projects.models import Project + log = logging.getLogger(__name__) GITHUB_EVENT_HEADER = 'HTTP_X_GITHUB_EVENT' diff --git a/readthedocs/restapi/views/model_views.py b/readthedocs/restapi/views/model_views.py index 3e9250314..4e021d5e2 100644 --- a/readthedocs/restapi/views/model_views.py +++ b/readthedocs/restapi/views/model_views.py @@ -1,13 +1,9 @@ # -*- coding: utf-8 -*- """Endpoints for listing Projects, Versions, Builds, etc.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging from allauth.socialaccount.models import SocialAccount -from builtins import str from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from rest_framework import decorators, permissions, status, viewsets @@ -25,12 +21,25 @@ from readthedocs.projects.version_handling import determine_stable_version from .. import utils as api_utils from ..permissions import ( - APIPermission, APIRestrictedPermission, IsOwner, RelatedProjectIsOwner) + APIPermission, + APIRestrictedPermission, + IsOwner, + RelatedProjectIsOwner, +) from ..serializers import ( - BuildAdminSerializer, BuildCommandSerializer, BuildSerializer, - DomainSerializer, ProjectAdminSerializer, ProjectSerializer, - RemoteOrganizationSerializer, RemoteRepositorySerializer, - SocialAccountSerializer, VersionAdminSerializer, VersionSerializer) + BuildAdminSerializer, + BuildCommandSerializer, + BuildSerializer, + DomainSerializer, + ProjectAdminSerializer, + ProjectSerializer, + RemoteOrganizationSerializer, + RemoteRepositorySerializer, + SocialAccountSerializer, + VersionAdminSerializer, + VersionSerializer, +) + log = logging.getLogger(__name__) diff --git a/readthedocs/restapi/views/search_views.py b/readthedocs/restapi/views/search_views.py index abe361740..771a3069b 100644 --- a/readthedocs/restapi/views/search_views.py +++ b/readthedocs/restapi/views/search_views.py @@ -1,6 +1,5 @@ """Endpoints related to searching through projects, sections, etc.""" -from __future__ import absolute_import import logging from rest_framework import decorators, permissions, status @@ -10,8 +9,8 @@ from rest_framework.response import Response from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Version from readthedocs.projects.models import Project, ProjectRelationship -from readthedocs.search.lib import search_file, search_project, search_section from readthedocs.restapi import utils +from readthedocs.search.lib import search_file, search_project, search_section log = logging.getLogger(__name__) diff --git a/readthedocs/restapi/views/task_views.py b/readthedocs/restapi/views/task_views.py index 475fb17bd..0213aeb75 100644 --- a/readthedocs/restapi/views/task_views.py +++ b/readthedocs/restapi/views/task_views.py @@ -1,12 +1,5 @@ """Endpoints relating to task/job status, etc.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging from django.urls import reverse @@ -18,6 +11,7 @@ from rest_framework.response import Response from readthedocs.core.utils.tasks import TaskNoPermission, get_public_task_data from readthedocs.oauth import tasks + log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py index c6813f22f..b5ab43115 100644 --- a/readthedocs/rtd_tests/base.py +++ b/readthedocs/rtd_tests/base.py @@ -1,19 +1,16 @@ """Base classes and mixins for unit tests.""" -from __future__ import absolute_import -from builtins import object +import logging import os import shutil -import logging import tempfile from collections import OrderedDict -from mock import patch from django.conf import settings -from django.test import TestCase, RequestFactory from django.contrib.auth.models import AnonymousUser from django.contrib.messages.storage.fallback import FallbackStorage from django.contrib.sessions.middleware import SessionMiddleware -import six +from django.test import RequestFactory, TestCase +from mock import patch log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index b448eab9a..7bdfdbe06 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - from datetime import datetime from recommonmark.parser import CommonMarkParser diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py index 3b2a28f49..a5aa1b8fe 100644 --- a/readthedocs/rtd_tests/mocks/environment.py +++ b/readthedocs/rtd_tests/mocks/environment.py @@ -1,9 +1,6 @@ # pylint: disable=missing-docstring -from __future__ import absolute_import -from builtins import object import mock - class EnvironmentMockGroup: """Mock out necessary environment pieces""" diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py index 68d1e5443..3dd54d663 100644 --- a/readthedocs/rtd_tests/mocks/mock_api.py +++ b/readthedocs/rtd_tests/mocks/mock_api.py @@ -1,8 +1,7 @@ """Mock versions of many API-related classes.""" -from __future__ import absolute_import -from builtins import object -from contextlib import contextmanager import json +from contextlib import contextmanager + import mock # Mock tastypi API. diff --git a/readthedocs/rtd_tests/mocks/paths.py b/readthedocs/rtd_tests/mocks/paths.py index 34fa7e595..ee8b29b7a 100644 --- a/readthedocs/rtd_tests/mocks/paths.py +++ b/readthedocs/rtd_tests/mocks/paths.py @@ -1,9 +1,8 @@ """Context managers to patch os.path.exists calls.""" -from __future__ import absolute_import import os import re -import mock +import mock def fake_paths(check): """ diff --git a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py index 54111314c..acdc99df3 100644 --- a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py +++ b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py @@ -1,14 +1,13 @@ -import mock import django_dynamic_fixture as fixture +import mock +from django import urls from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.auth.models import User -from django import urls from django.test import TestCase from readthedocs.core.models import UserProfile from readthedocs.projects.models import Project - class ProjectAdminActionsTest(TestCase): @classmethod diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index af63a2378..44f2a0374 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -1,22 +1,14 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import base64 import datetime import json import mock from allauth.socialaccount.models import SocialAccount -from builtins import str from django.contrib.auth.models import User -from django.urls import reverse from django.http import QueryDict from django.test import TestCase +from django.urls import reverse from django.utils import six from django_dynamic_fixture import get from rest_framework import status diff --git a/readthedocs/rtd_tests/tests/test_api_permissions.py b/readthedocs/rtd_tests/tests/test_api_permissions.py index d3666927a..40d348c6c 100644 --- a/readthedocs/rtd_tests/tests/test_api_permissions.py +++ b/readthedocs/rtd_tests/tests/test_api_permissions.py @@ -1,10 +1,9 @@ -from __future__ import absolute_import from functools import partial -from mock import Mock from unittest import TestCase -from readthedocs.restapi.permissions import APIRestrictedPermission +from mock import Mock +from readthedocs.restapi.permissions import APIRestrictedPermission class APIRestrictedPermissionTests(TestCase): def get_request(self, method, is_admin): diff --git a/readthedocs/rtd_tests/tests/test_api_version_compare.py b/readthedocs/rtd_tests/tests/test_api_version_compare.py index 0daec18a0..df4df452c 100644 --- a/readthedocs/rtd_tests/tests/test_api_version_compare.py +++ b/readthedocs/rtd_tests/tests/test_api_version_compare.py @@ -1,11 +1,9 @@ -from __future__ import absolute_import from django.test import TestCase from readthedocs.builds.constants import LATEST from readthedocs.projects.models import Project from readthedocs.restapi.views.footer_views import get_version_compare_data - class VersionCompareTests(TestCase): fixtures = ['eric.json', 'test_data.json'] diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index 34510778f..76eb07d37 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -1,19 +1,11 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import os from os.path import exists from tempfile import mkdtemp import django_dynamic_fixture as fixture import pytest -import six from django.contrib.auth.models import User from mock import Mock, patch @@ -30,7 +22,6 @@ from readthedocs.rtd_tests.utils import ( make_test_hg, ) - class TestGitBackend(RTDTestCase): def setUp(self): git_repo = make_test_git() diff --git a/readthedocs/rtd_tests/tests/test_backend_svn.py b/readthedocs/rtd_tests/tests/test_backend_svn.py index 8de9ea776..e3cf9e2e2 100644 --- a/readthedocs/rtd_tests/tests/test_backend_svn.py +++ b/readthedocs/rtd_tests/tests/test_backend_svn.py @@ -1,19 +1,12 @@ # -*- coding: utf-8 -*- """Tests For SVN""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - -from mock import patch from django_dynamic_fixture import get +from mock import patch -from readthedocs.rtd_tests.base import RTDTestCase -from readthedocs.projects.models import Project from readthedocs.builds.models import Version +from readthedocs.projects.models import Project +from readthedocs.rtd_tests.base import RTDTestCase from readthedocs.vcs_support.backends.svn import Backend as SvnBackend class TestSvnBackend(RTDTestCase): diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py index 9085d6f79..90a28ce25 100644 --- a/readthedocs/rtd_tests/tests/test_build_config.py +++ b/readthedocs/rtd_tests/tests/test_build_config.py @@ -1,13 +1,11 @@ -from __future__ import division, print_function, unicode_literals - from os import path import pytest -import six import yamale -from readthedocs.config.tests import utils from yamale.validators import DefaultValidators, Validator +from readthedocs.config.tests import utils + V2_SCHEMA = path.join( path.dirname(__file__), '../fixtures/spec/v2/schema.yml' diff --git a/readthedocs/rtd_tests/tests/test_build_forms.py b/readthedocs/rtd_tests/tests/test_build_forms.py index b0ba6890d..d69ca2894 100644 --- a/readthedocs/rtd_tests/tests/test_build_forms.py +++ b/readthedocs/rtd_tests/tests/test_build_forms.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - from django.test import TestCase from django_dynamic_fixture import get @@ -10,7 +8,6 @@ from readthedocs.builds.models import Version from readthedocs.projects.constants import PRIVATE from readthedocs.projects.models import Project - class TestVersionForm(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_build_notifications.py b/readthedocs/rtd_tests/tests/test_build_notifications.py index 946058946..b276d1d93 100644 --- a/readthedocs/rtd_tests/tests/test_build_notifications.py +++ b/readthedocs/rtd_tests/tests/test_build_notifications.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- """Notifications sent after build is completed.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import django_dynamic_fixture as fixture from django.core import mail from django.test import TestCase from mock import patch from readthedocs.builds.models import Build, Version -from readthedocs.projects.models import Project, EmailHook, WebHook -from readthedocs.projects.tasks import send_notifications from readthedocs.projects.forms import WebHookForm - +from readthedocs.projects.models import EmailHook, Project, WebHook +from readthedocs.projects.tasks import send_notifications class BuildNotificationsTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 53e222203..6f8808bc1 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -1,12 +1,6 @@ -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) +import os import mock -import os from django.test import TestCase from django_dynamic_fixture import fixture, get @@ -14,13 +8,12 @@ from readthedocs.builds.models import Build, Version from readthedocs.doc_builder.config import load_yaml_config from readthedocs.doc_builder.environments import LocalBuildEnvironment from readthedocs.doc_builder.python_environments import Virtualenv -from readthedocs.projects.models import Project, EnvironmentVariable +from readthedocs.projects.models import EnvironmentVariable, Project from readthedocs.projects.tasks import UpdateDocsTaskStep from readthedocs.rtd_tests.tests.test_config_integration import create_load from ..mocks.environment import EnvironmentMockGroup - class BuildEnvironmentTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index e718e220b..9191250e8 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - import os import shutil from os.path import exists @@ -8,20 +6,21 @@ from tempfile import mkdtemp from django.contrib.auth.models import User from django_dynamic_fixture import get -from mock import patch, MagicMock +from mock import MagicMock, patch from readthedocs.builds.constants import LATEST -from readthedocs.projects.exceptions import RepositoryError from readthedocs.builds.models import Build -from readthedocs.projects.models import Project from readthedocs.projects import tasks - -from readthedocs.rtd_tests.utils import ( - create_git_branch, create_git_tag, delete_git_branch) -from readthedocs.rtd_tests.utils import make_test_git +from readthedocs.projects.exceptions import RepositoryError +from readthedocs.projects.models import Project from readthedocs.rtd_tests.base import RTDTestCase from readthedocs.rtd_tests.mocks.mock_api import mock_api - +from readthedocs.rtd_tests.utils import ( + create_git_branch, + create_git_tag, + delete_git_branch, + make_test_git, +) class TestCeleryBuilding(RTDTestCase): diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 8474c4bea..e0b93243c 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import tempfile from os import path @@ -22,7 +19,6 @@ from readthedocs.projects import tasks from readthedocs.projects.models import Feature, Project from readthedocs.rtd_tests.utils import create_git_submodule, make_git_repo - def create_load(config=None): """ Mock out the function of the build load function. diff --git a/readthedocs/rtd_tests/tests/test_core_tags.py b/readthedocs/rtd_tests/tests/test_core_tags.py index 851fd40d1..20dd966ad 100644 --- a/readthedocs/rtd_tests/tests/test_core_tags.py +++ b/readthedocs/rtd_tests/tests/test_core_tags.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import import mock import pytest - from django.conf import settings from django.test import TestCase from django.test.utils import override_settings -from readthedocs.projects.models import Project from readthedocs.builds.constants import LATEST from readthedocs.core.templatetags import core_tags - +from readthedocs.projects.models import Project @override_settings(USE_SUBDOMAIN=False, PRODUCTION_DOMAIN='readthedocs.org') class CoreTagsTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_core_utils.py b/readthedocs/rtd_tests/tests/test_core_utils.py index 85e7d7355..e480f6398 100644 --- a/readthedocs/rtd_tests/tests/test_core_utils.py +++ b/readthedocs/rtd_tests/tests/test_core_utils.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- """Test core util functions""" -from __future__ import absolute_import import mock - -from django_dynamic_fixture import get from django.test import TestCase +from django_dynamic_fixture import get -from readthedocs.projects.models import Project from readthedocs.builds.models import Version -from readthedocs.core.utils import trigger_build, slugify - +from readthedocs.core.utils import slugify, trigger_build +from readthedocs.projects.models import Project class CoreUtilTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index a384f5d00..383237d26 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import os import tempfile from collections import namedtuple @@ -22,7 +19,6 @@ from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Feature, Project - class SphinxBuilderTest(TestCase): fixtures = ['test_data'] diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 1b8f11c38..1a1dbb579 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -5,13 +5,6 @@ Things to know: * raw subprocess calls like .communicate expects bytes * the Command wrappers encapsulate the bytes and expose unicode """ -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import json import os import re @@ -20,7 +13,6 @@ import uuid import mock import pytest -from builtins import str from django.test import TestCase from django_dynamic_fixture import get from docker.errors import APIError as DockerAPIError @@ -43,7 +35,6 @@ from readthedocs.rtd_tests.mocks.environment import EnvironmentMockGroup from readthedocs.rtd_tests.mocks.paths import fake_paths_lookup from readthedocs.rtd_tests.tests.test_config_integration import create_load - DUMMY_BUILD_ID = 123 SAMPLE_UNICODE = 'HérÉ îß sömê ünïçó∂é' SAMPLE_UTF8_BYTES = SAMPLE_UNICODE.encode('utf-8') diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index 123647201..8d8e45819 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -1,18 +1,15 @@ -from __future__ import absolute_import -import mock import django_dynamic_fixture as fixture - +import mock +from django.conf import settings from django.contrib.auth.models import User +from django.http import Http404 from django.test import TestCase from django.test.utils import override_settings -from django.http import Http404 -from django.conf import settings -from readthedocs.rtd_tests.base import RequestFactoryTestMixin +from readthedocs.core.views.serve import _serve_symlink_docs from readthedocs.projects import constants from readthedocs.projects.models import Project -from readthedocs.core.views.serve import _serve_symlink_docs - +from readthedocs.rtd_tests.base import RequestFactoryTestMixin @override_settings( USE_SUBDOMAIN=False, PUBLIC_DOMAIN='public.readthedocs.org', DEBUG=False diff --git a/readthedocs/rtd_tests/tests/test_domains.py b/readthedocs/rtd_tests/tests/test_domains.py index 5e487ce2c..963b07a91 100644 --- a/readthedocs/rtd_tests/tests/test_domains.py +++ b/readthedocs/rtd_tests/tests/test_domains.py @@ -1,19 +1,16 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import import json from django.core.cache import cache from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings - from django_dynamic_fixture import get from readthedocs.core.middleware import SubdomainMiddleware -from readthedocs.projects.models import Project, Domain from readthedocs.projects.forms import DomainForm - +from readthedocs.projects.models import Domain, Project class MiddlewareTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py index d3c66552b..21017e308 100644 --- a/readthedocs/rtd_tests/tests/test_extend.py +++ b/readthedocs/rtd_tests/tests/test_extend.py @@ -1,10 +1,9 @@ -from __future__ import absolute_import -from builtins import object from django.test import TestCase, override_settings -from readthedocs.core.utils.extend import (SettingsOverrideObject, - get_override_class) - +from readthedocs.core.utils.extend import ( + SettingsOverrideObject, + get_override_class, +) # Top level to ensure module name is correct class FooBase: diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index ec5ca4d75..692e85cd1 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -1,11 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import mock from django.test import TestCase from rest_framework.test import APIRequestFactory, APITestCase @@ -20,7 +13,6 @@ from readthedocs.restapi.views.footer_views import ( ) from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex - class Testmaker(APITestCase): fixtures = ['test_data'] url = '/api/v2/footer_html/?project=pip&version=latest&page=index&docroot=/' diff --git a/readthedocs/rtd_tests/tests/test_gold.py b/readthedocs/rtd_tests/tests/test_gold.py index 63f50f8d6..9475f4fd1 100644 --- a/readthedocs/rtd_tests/tests/test_gold.py +++ b/readthedocs/rtd_tests/tests/test_gold.py @@ -1,15 +1,11 @@ -from __future__ import absolute_import - -from django.urls import reverse from django.test import TestCase +from django.urls import reverse +from django_dynamic_fixture import fixture, get -from django_dynamic_fixture import get, fixture - -from readthedocs.gold.models import GoldUser, LEVEL_CHOICES +from readthedocs.gold.models import LEVEL_CHOICES, GoldUser from readthedocs.projects.models import Project from readthedocs.rtd_tests.utils import create_user - class GoldViewTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_imported_file.py b/readthedocs/rtd_tests/tests/test_imported_file.py index 81f5e2a68..d005fc9a1 100644 --- a/readthedocs/rtd_tests/tests/test_imported_file.py +++ b/readthedocs/rtd_tests/tests/test_imported_file.py @@ -1,9 +1,9 @@ -from __future__ import absolute_import import os + from django.test import TestCase +from readthedocs.projects.models import ImportedFile, Project from readthedocs.projects.tasks import _manage_imported_files -from readthedocs.projects.models import Project, ImportedFile base_dir = os.path.dirname(os.path.dirname(__file__)) diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py index 17f9b2912..360fdb0f1 100644 --- a/readthedocs/rtd_tests/tests/test_integrations.py +++ b/readthedocs/rtd_tests/tests/test_integrations.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import - -from builtins import range import django_dynamic_fixture as fixture -from django.test import TestCase, RequestFactory from django.contrib.contenttypes.models import ContentType -from rest_framework.test import APIClient -from rest_framework.test import APIRequestFactory +from django.test import RequestFactory, TestCase from rest_framework.response import Response +from rest_framework.test import APIClient, APIRequestFactory from readthedocs.integrations.models import ( - HttpExchange, Integration, GitHubWebhook + GitHubWebhook, + HttpExchange, + Integration, ) from readthedocs.projects.models import Project - class HttpExchangeTests(TestCase): """Test HttpExchange model by using existing views diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py index 0bfe9b563..f7e5d6076 100644 --- a/readthedocs/rtd_tests/tests/test_middleware.py +++ b/readthedocs/rtd_tests/tests/test_middleware.py @@ -1,26 +1,20 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import - -from django.http import Http404 +from corsheaders.middleware import CorsMiddleware from django.conf import settings from django.core.cache import cache -from django.urls.base import get_urlconf, set_urlconf +from django.http import Http404 from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings - +from django.urls.base import get_urlconf, set_urlconf from django_dynamic_fixture import get - -from corsheaders.middleware import CorsMiddleware from mock import patch from readthedocs.core.middleware import SubdomainMiddleware -from readthedocs.projects.models import Project, ProjectRelationship, Domain - +from readthedocs.projects.models import Domain, Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user - @override_settings(USE_SUBDOMAIN=True) class MiddlewareTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index adae07e49..ddc8c9ada 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -1,19 +1,21 @@ # -*- coding: utf-8 -*- """Notification tests""" -from __future__ import absolute_import -import mock import django_dynamic_fixture as fixture +import mock +from django.contrib.auth.models import AnonymousUser, User from django.test import TestCase from django.test.utils import override_settings -from django.contrib.auth.models import User, AnonymousUser from messages_extends.models import Message as PersistentMessage +from readthedocs.builds.models import Build from readthedocs.notifications import Notification, SiteNotification from readthedocs.notifications.backends import EmailBackend, SiteBackend -from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT -from readthedocs.builds.models import Build - +from readthedocs.notifications.constants import ( + ERROR, + INFO_NON_PERSISTENT, + WARNING_NON_PERSISTENT, +) @override_settings( NOTIFICATION_BACKENDS=[ diff --git a/readthedocs/rtd_tests/tests/test_oauth.py b/readthedocs/rtd_tests/tests/test_oauth.py index fedbaf0d4..45c2983bb 100644 --- a/readthedocs/rtd_tests/tests/test_oauth.py +++ b/readthedocs/rtd_tests/tests/test_oauth.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import mock from django.conf import settings from django.contrib.auth.models import User @@ -10,11 +7,13 @@ from django.test.utils import override_settings from readthedocs.oauth.models import RemoteOrganization, RemoteRepository from readthedocs.oauth.services import ( - BitbucketService, GitHubService, GitLabService) + BitbucketService, + GitHubService, + GitLabService, +) from readthedocs.projects import constants from readthedocs.projects.models import Project - class GitHubOAuthTests(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index 86297dbe4..97eceae19 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import json import logging @@ -9,8 +7,7 @@ from django_dynamic_fixture import get from future.backports.urllib.parse import urlencode from readthedocs.builds.models import Version -from readthedocs.projects.models import Project, Feature - +from readthedocs.projects.models import Feature, Project log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/tests/test_privacy.py b/readthedocs/rtd_tests/tests/test_privacy.py index 8a7359c90..7844ece2e 100644 --- a/readthedocs/rtd_tests/tests/test_privacy.py +++ b/readthedocs/rtd_tests/tests/test_privacy.py @@ -1,17 +1,16 @@ -from __future__ import absolute_import -import logging import json -import mock +import logging +import mock +from django.contrib.auth.models import User from django.test import TestCase from django.test.utils import override_settings -from django.contrib.auth.models import User from readthedocs.builds.constants import LATEST -from readthedocs.builds.models import Version, Build -from readthedocs.projects.models import Project -from readthedocs.projects.forms import UpdateProjectForm +from readthedocs.builds.models import Build, Version from readthedocs.projects import tasks +from readthedocs.projects.forms import UpdateProjectForm +from readthedocs.projects.models import Project log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index f793f8c51..ba71d66bf 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -1,24 +1,20 @@ -from __future__ import absolute_import -from __future__ import print_function import re +import mock from allauth.socialaccount.models import SocialAccount -from builtins import object from django.contrib.admindocs.views import extract_views_from_urlpatterns from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import get -import mock from taggit.models import Tag from readthedocs.builds.models import Build, BuildCommandResult from readthedocs.core.utils.tasks import TaskNoPermission from readthedocs.integrations.models import HttpExchange, Integration -from readthedocs.projects.models import Project, Domain -from readthedocs.oauth.models import RemoteRepository, RemoteOrganization +from readthedocs.oauth.models import RemoteOrganization, RemoteRepository +from readthedocs.projects.models import Domain, Project from readthedocs.rtd_tests.utils import create_user - class URLAccessMixin: default_kwargs = {} diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py index 9e3a75a44..239f093be 100644 --- a/readthedocs/rtd_tests/tests/test_profile_views.py +++ b/readthedocs/rtd_tests/tests/test_profile_views.py @@ -1,11 +1,8 @@ -from __future__ import division, print_function, unicode_literals - from django.contrib.auth.models import User -from django.urls import reverse from django.test import TestCase +from django.urls import reverse from django_dynamic_fixture import get - class ProfileViewsTest(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index bbbd43a11..b6bee71d0 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -1,27 +1,27 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import datetime import json from django.contrib.auth.models import User from django.forms.models import model_to_dict from django.test import TestCase -from django_dynamic_fixture import get from django.utils import timezone +from django_dynamic_fixture import get from mock import patch from rest_framework.reverse import reverse from readthedocs.builds.constants import ( - BUILD_STATE_CLONING, BUILD_STATE_FINISHED, BUILD_STATE_TRIGGERED, LATEST) + BUILD_STATE_CLONING, + BUILD_STATE_FINISHED, + BUILD_STATE_TRIGGERED, + LATEST, +) from readthedocs.builds.models import Build from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Project from readthedocs.projects.tasks import finish_inactive_builds from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex - class ProjectMixin: fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index e7fefb5bb..bc423b8d4 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -1,12 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import mock from django.contrib.auth.models import User from django.test import TestCase @@ -33,7 +26,6 @@ from readthedocs.projects.forms import ( ) from readthedocs.projects.models import Project - class TestProjectForms(TestCase): @mock.patch.object(ClassifierValidator, '__call__') diff --git a/readthedocs/rtd_tests/tests/test_project_querysets.py b/readthedocs/rtd_tests/tests/test_project_querysets.py index 682e54cd9..a9193e47e 100644 --- a/readthedocs/rtd_tests/tests/test_project_querysets.py +++ b/readthedocs/rtd_tests/tests/test_project_querysets.py @@ -1,14 +1,13 @@ -from __future__ import absolute_import - from datetime import timedelta import django_dynamic_fixture as fixture from django.test import TestCase -from readthedocs.projects.models import Project, ProjectRelationship, Feature -from readthedocs.projects.querysets import (ParentRelatedProjectQuerySet, - ChildRelatedProjectQuerySet) - +from readthedocs.projects.models import Feature, Project, ProjectRelationship +from readthedocs.projects.querysets import ( + ChildRelatedProjectQuerySet, + ParentRelatedProjectQuerySet, +) class ProjectQuerySetTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index e35ca3874..bd9cb76ef 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -1,22 +1,23 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from builtins import object import os import shutil import tempfile import mock from django.conf import settings -from django.urls import reverse from django.test import TestCase, override_settings +from django.urls import reverse from django_dynamic_fixture import get from readthedocs.builds.models import Version -from readthedocs.projects.models import Project, Domain -from readthedocs.projects.tasks import broadcast_remove_orphan_symlinks, remove_orphan_symlinks, symlink_project -from readthedocs.core.symlink import PublicSymlink, PrivateSymlink - +from readthedocs.core.symlink import PrivateSymlink, PublicSymlink +from readthedocs.projects.models import Domain, Project +from readthedocs.projects.tasks import ( + broadcast_remove_orphan_symlinks, + remove_orphan_symlinks, + symlink_project, +) def get_filesystem(path, top_level_path=None): """Recurse into path, return dictionary mapping of path and files diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py index 124fd018c..ecf3c8bd6 100644 --- a/readthedocs/rtd_tests/tests/test_project_views.py +++ b/readthedocs/rtd_tests/tests/test_project_views.py @@ -1,29 +1,27 @@ -from __future__ import absolute_import from datetime import timedelta - -from mock import patch -from django.test import TestCase from django.contrib.auth.models import User from django.contrib.messages import constants as message_const -from django.urls import reverse from django.http.response import HttpResponseRedirect -from django.views.generic.base import ContextMixin +from django.test import TestCase +from django.urls import reverse from django.utils import timezone +from django.views.generic.base import ContextMixin from django_dynamic_fixture import get, new - -import six +from mock import patch from readthedocs.builds.models import Build, Version -from readthedocs.rtd_tests.base import (WizardTestCase, MockBuildTestCase, - RequestFactoryTestMixin) from readthedocs.oauth.models import RemoteRepository -from readthedocs.projects.exceptions import ProjectSpamError -from readthedocs.projects.models import Project, Domain -from readthedocs.projects.views.private import ImportWizardView -from readthedocs.projects.views.mixins import ProjectRelationMixin from readthedocs.projects import tasks - +from readthedocs.projects.exceptions import ProjectSpamError +from readthedocs.projects.models import Domain, Project +from readthedocs.projects.views.mixins import ProjectRelationMixin +from readthedocs.projects.views.private import ImportWizardView +from readthedocs.rtd_tests.base import ( + MockBuildTestCase, + RequestFactoryTestMixin, + WizardTestCase, +) @patch('readthedocs.projects.views.private.trigger_build', lambda x: None) class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): diff --git a/readthedocs/rtd_tests/tests/test_redirects.py b/readthedocs/rtd_tests/tests/test_redirects.py index 335c8feba..d53d55125 100644 --- a/readthedocs/rtd_tests/tests/test_redirects.py +++ b/readthedocs/rtd_tests/tests/test_redirects.py @@ -1,10 +1,9 @@ -from __future__ import absolute_import +import logging + from django.http import Http404 from django.test import TestCase from django.test.utils import override_settings - -from django_dynamic_fixture import get -from django_dynamic_fixture import fixture +from django_dynamic_fixture import fixture, get from mock import patch from readthedocs.builds.constants import LATEST @@ -12,9 +11,6 @@ from readthedocs.builds.models import Version from readthedocs.projects.models import Project from readthedocs.redirects.models import Redirect -import logging - - @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False, APPEND_SLASH=False) class RedirectTests(TestCase): fixtures = ["eric", "test_data"] diff --git a/readthedocs/rtd_tests/tests/test_repo_parsing.py b/readthedocs/rtd_tests/tests/test_repo_parsing.py index f946db61e..d77c51ecb 100644 --- a/readthedocs/rtd_tests/tests/test_repo_parsing.py +++ b/readthedocs/rtd_tests/tests/test_repo_parsing.py @@ -1,12 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - from django.test import TestCase from readthedocs.projects.models import Project - class TestRepoParsing(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py index 841da05c1..929ccc8db 100644 --- a/readthedocs/rtd_tests/tests/test_resolver.py +++ b/readthedocs/rtd_tests/tests/test_resolver.py @@ -1,19 +1,18 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import django_dynamic_fixture as fixture import mock from django.test import TestCase, override_settings from readthedocs.core.resolver import ( - Resolver, resolve, resolve_domain, resolve_path + Resolver, + resolve, + resolve_domain, + resolve_path, ) from readthedocs.projects.constants import PRIVATE from readthedocs.projects.models import Domain, Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user - @override_settings(PUBLIC_DOMAIN='readthedocs.org') class ResolverBase(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_restapi_client.py b/readthedocs/rtd_tests/tests/test_restapi_client.py index 88906fdc1..1aeca51ee 100644 --- a/readthedocs/rtd_tests/tests/test_restapi_client.py +++ b/readthedocs/rtd_tests/tests/test_restapi_client.py @@ -1,12 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, unicode_literals) - from django.test import TestCase from readthedocs.restapi.client import DrfJsonSerializer - class TestDrfJsonSerializer(TestCase): data = { 'proper': 'json' diff --git a/readthedocs/rtd_tests/tests/test_search_json_parsing.py b/readthedocs/rtd_tests/tests/test_search_json_parsing.py index fb91d31b2..b51ea834f 100644 --- a/readthedocs/rtd_tests/tests/test_search_json_parsing.py +++ b/readthedocs/rtd_tests/tests/test_search_json_parsing.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import os from django.test import TestCase diff --git a/readthedocs/rtd_tests/tests/test_single_version.py b/readthedocs/rtd_tests/tests/test_single_version.py index faee5f585..de1bc9300 100644 --- a/readthedocs/rtd_tests/tests/test_single_version.py +++ b/readthedocs/rtd_tests/tests/test_single_version.py @@ -1,12 +1,9 @@ -from __future__ import absolute_import +import django_dynamic_fixture as fixture from django.test import TestCase from django.test.utils import override_settings -import django_dynamic_fixture as fixture - from readthedocs.projects.models import Project - @override_settings( USE_SUBDOMAIN=True, PUBLIC_DOMAIN='public.readthedocs.org', SERVE_PUBLIC_DOCS=True ) diff --git a/readthedocs/rtd_tests/tests/test_subprojects.py b/readthedocs/rtd_tests/tests/test_subprojects.py index 081bd89a0..542b2d8b0 100644 --- a/readthedocs/rtd_tests/tests/test_subprojects.py +++ b/readthedocs/rtd_tests/tests/test_subprojects.py @@ -1,7 +1,5 @@ -from __future__ import absolute_import - -import mock import django_dynamic_fixture as fixture +import mock from django.contrib.auth.models import User from django.test import TestCase from django.test.utils import override_settings @@ -10,7 +8,6 @@ from readthedocs.projects.forms import ProjectRelationshipForm from readthedocs.projects.models import Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user - class SubprojectFormTests(TestCase): def test_empty_child(self): diff --git a/readthedocs/rtd_tests/tests/test_sync_versions.py b/readthedocs/rtd_tests/tests/test_sync_versions.py index 533ad4247..731cc0675 100644 --- a/readthedocs/rtd_tests/tests/test_sync_versions.py +++ b/readthedocs/rtd_tests/tests/test_sync_versions.py @@ -1,23 +1,15 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals -) - import json +import pytest from django.test import TestCase from django.urls import reverse -import pytest from readthedocs.builds.constants import BRANCH, STABLE, TAG from readthedocs.builds.models import Version from readthedocs.projects.models import Project - class TestSyncVersions(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py index cb4204e28..55a4a8400 100644 --- a/readthedocs/rtd_tests/tests/test_urls.py +++ b/readthedocs/rtd_tests/tests/test_urls.py @@ -1,8 +1,5 @@ -from __future__ import absolute_import - -from django.urls import reverse, NoReverseMatch from django.test import TestCase - +from django.urls import NoReverseMatch, reverse class WipeUrlTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py index 943843c22..23875955f 100644 --- a/readthedocs/rtd_tests/tests/test_version_commit_name.py +++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py @@ -1,18 +1,11 @@ -from __future__ import absolute_import from django.test import TestCase -from django_dynamic_fixture import get -from django_dynamic_fixture import new +from django_dynamic_fixture import get, new -from readthedocs.builds.constants import BRANCH -from readthedocs.builds.constants import LATEST -from readthedocs.builds.constants import STABLE -from readthedocs.builds.constants import TAG +from readthedocs.builds.constants import BRANCH, LATEST, STABLE, TAG from readthedocs.builds.models import Version -from readthedocs.projects.constants import REPO_TYPE_GIT -from readthedocs.projects.constants import REPO_TYPE_HG +from readthedocs.projects.constants import REPO_TYPE_GIT, REPO_TYPE_HG from readthedocs.projects.models import Project - class VersionCommitNameTests(TestCase): def test_branch_name_unicode_non_ascii(self): unicode_name = b'abc_\xd1\x84_\xe2\x99\x98'.decode('utf-8') diff --git a/readthedocs/rtd_tests/tests/test_version_config.py b/readthedocs/rtd_tests/tests/test_version_config.py index 82286ade4..4fd8d0854 100644 --- a/readthedocs/rtd_tests/tests/test_version_config.py +++ b/readthedocs/rtd_tests/tests/test_version_config.py @@ -1,12 +1,9 @@ -from __future__ import division, print_function, unicode_literals - from django.test import TestCase from django_dynamic_fixture import get from readthedocs.builds.models import Build, Version from readthedocs.projects.models import Project - class VersionConfigTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py index 31cf2ca72..44d8500f2 100644 --- a/readthedocs/rtd_tests/tests/test_version_slug.py +++ b/readthedocs/rtd_tests/tests/test_version_slug.py @@ -1,13 +1,11 @@ -from __future__ import absolute_import import re + from django.test import TestCase from readthedocs.builds.models import Version -from readthedocs.builds.version_slug import VersionSlugField -from readthedocs.builds.version_slug import VERSION_SLUG_REGEX +from readthedocs.builds.version_slug import VERSION_SLUG_REGEX, VersionSlugField from readthedocs.projects.models import Project - class VersionSlugPatternTests(TestCase): pattern = re.compile('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX)) diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py index e68989e14..dd64a438a 100644 --- a/readthedocs/rtd_tests/tests/test_views.py +++ b/readthedocs/rtd_tests/tests/test_views.py @@ -1,15 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import mock from django.contrib.auth.models import User -from django.urls import reverse from django.test import TestCase +from django.urls import reverse from django.utils.six.moves.urllib.parse import urlsplit from django_dynamic_fixture import get, new diff --git a/readthedocs/rtd_tests/utils.py b/readthedocs/rtd_tests/utils.py index 1908ef0cc..a5768a19d 100644 --- a/readthedocs/rtd_tests/utils.py +++ b/readthedocs/rtd_tests/utils.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Utility functions for use in tests.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import subprocess import textwrap diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py index 189f441d8..65fc14643 100644 --- a/readthedocs/search/indexes.py +++ b/readthedocs/search/indexes.py @@ -14,16 +14,11 @@ Django settings that should be defined: TODO: Handle page removal case in Page. """ -from __future__ import absolute_import -from builtins import object - +from django.conf import settings from django.utils import timezone - from elasticsearch import Elasticsearch, exceptions from elasticsearch.helpers import bulk_index -from django.conf import settings - class Index: diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 8500a829b..b5ebf8754 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -1,17 +1,17 @@ """Utilities related to searching Elastic.""" -from __future__ import absolute_import -from __future__ import print_function from pprint import pprint from django.conf import settings -from .indexes import PageIndex, ProjectIndex, SectionIndex - from readthedocs.builds.constants import LATEST from readthedocs.projects.models import Project -from readthedocs.search.signals import (before_project_search, - before_file_search, - before_section_search) +from readthedocs.search.signals import ( + before_file_search, + before_project_search, + before_section_search, +) + +from .indexes import PageIndex, ProjectIndex, SectionIndex def search_project(request, query, language=None): diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py index baa4abe5e..4ad53beef 100644 --- a/readthedocs/search/parse_json.py +++ b/readthedocs/search/parse_json.py @@ -1,17 +1,15 @@ # -*- coding: utf-8 -*- """Functions related to converting content into dict/JSON structures.""" -from __future__ import absolute_import - -import logging import codecs import fnmatch import json +import logging import os -from builtins import next, range # pylint: disable=redefined-builtin from pyquery import PyQuery + log = logging.getLogger(__name__) diff --git a/readthedocs/search/signals.py b/readthedocs/search/signals.py index 6abdf64cc..7f599c356 100644 --- a/readthedocs/search/signals.py +++ b/readthedocs/search/signals.py @@ -1,7 +1,7 @@ """We define custom Django signals to trigger before executing searches.""" -from __future__ import absolute_import import django.dispatch + before_project_search = django.dispatch.Signal(providing_args=["body"]) before_file_search = django.dispatch.Signal(providing_args=["body"]) before_section_search = django.dispatch.Signal(providing_args=["body"]) diff --git a/readthedocs/search/tests/conftest.py b/readthedocs/search/tests/conftest.py index 59961f3a7..ab46332f7 100644 --- a/readthedocs/search/tests/conftest.py +++ b/readthedocs/search/tests/conftest.py @@ -6,9 +6,14 @@ import pytest from django_dynamic_fixture import G from readthedocs.projects.models import Project -from readthedocs.search.indexes import Index, ProjectIndex, PageIndex, SectionIndex -from .dummy_data import DUMMY_PAGE_JSON, ALL_PROJECTS +from readthedocs.search.indexes import ( + Index, + PageIndex, + ProjectIndex, + SectionIndex, +) +from .dummy_data import ALL_PROJECTS, DUMMY_PAGE_JSON @pytest.fixture(autouse=True) def mock_elastic_index(mocker): diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py index d5ac18baf..37072e949 100644 --- a/readthedocs/search/tests/test_views.py +++ b/readthedocs/search/tests/test_views.py @@ -11,7 +11,6 @@ from readthedocs.builds.models import Version from readthedocs.projects.models import Project from readthedocs.search.tests.utils import get_search_query_from_project_file - @pytest.mark.django_db @pytest.mark.search class TestElasticSearch: diff --git a/readthedocs/search/tests/utils.py b/readthedocs/search/tests/utils.py index a48ea83dd..90ce5f705 100644 --- a/readthedocs/search/tests/utils.py +++ b/readthedocs/search/tests/utils.py @@ -1,6 +1,5 @@ from readthedocs.search.tests.dummy_data import DUMMY_PAGE_JSON - def get_search_query_from_project_file(project_slug, page_num=0, data_type='title'): """Return search query from the project's page file. Query is generated from the value of `data_type` diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py index ec429e4ac..8e795a569 100644 --- a/readthedocs/search/utils.py +++ b/readthedocs/search/utils.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- """Utilities related to reading and generating indexable search content.""" -from __future__ import absolute_import - -import os -import fnmatch -import re import codecs -import logging +import fnmatch import json +import logging +import os +import re -from builtins import next, range from pyquery import PyQuery diff --git a/readthedocs/search/views.py b/readthedocs/search/views.py index e1725f87f..200dbadd7 100644 --- a/readthedocs/search/views.py +++ b/readthedocs/search/views.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- """Search views.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import collections import logging from pprint import pprint @@ -13,6 +10,7 @@ from django.shortcuts import render from readthedocs.builds.constants import LATEST from readthedocs.search import lib as search_lib + log = logging.getLogger(__name__) LOG_TEMPLATE = '(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}' diff --git a/readthedocs/urls.py b/readthedocs/urls.py index e441bc944..ba6e67ef3 100644 --- a/readthedocs/urls.py +++ b/readthedocs/urls.py @@ -1,26 +1,28 @@ # pylint: disable=missing-docstring -from __future__ import absolute_import - import os from functools import reduce from operator import add -from django.conf.urls import url, include -from django.contrib import admin from django.conf import settings +from django.conf.urls import include, url from django.conf.urls.static import static -from django.views.generic.base import TemplateView, RedirectView +from django.contrib import admin +from django.views.generic.base import RedirectView, TemplateView from tastypie.api import Api -from readthedocs.api.base import (ProjectResource, UserResource, - VersionResource, FileResource) -from readthedocs.core.urls import docs_urls, core_urls, deprecated_urls +from readthedocs.api.base import ( + FileResource, + ProjectResource, + UserResource, + VersionResource, +) +from readthedocs.core.urls import core_urls, deprecated_urls, docs_urls from readthedocs.core.views import ( HomepageView, SupportView, + do_not_track, server_error_404, server_error_500, - do_not_track, ) from readthedocs.search import views as search_views diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py index 9a309a807..9c9787040 100644 --- a/readthedocs/vcs_support/backends/bzr.py +++ b/readthedocs/vcs_support/backends/bzr.py @@ -1,19 +1,9 @@ # -*- coding: utf-8 -*- """Bazaar-related utilities.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import csv import re -from builtins import str # pylint: disable=redefined-builtin -from six import StringIO - from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index 80fafb89e..5d1b4eaa8 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -1,19 +1,11 @@ # -*- coding: utf-8 -*- """Git-related utilities.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os import re import git -from builtins import str from django.core.exceptions import ValidationError from git.exc import BadName, InvalidGitRepositoryError @@ -22,6 +14,7 @@ from readthedocs.projects.exceptions import RepositoryError from readthedocs.projects.validators import validate_submodule_url from readthedocs.vcs_support.base import BaseVCS, VCSVersion + log = logging.getLogger(__name__) diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py index e65d4c650..7bb252322 100644 --- a/readthedocs/vcs_support/backends/hg.py +++ b/readthedocs/vcs_support/backends/hg.py @@ -1,12 +1,5 @@ # -*- coding: utf-8 -*- """Mercurial-related utilities.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py index 2363116c6..b4ad61303 100644 --- a/readthedocs/vcs_support/backends/svn.py +++ b/readthedocs/vcs_support/backends/svn.py @@ -1,18 +1,8 @@ # -*- coding: utf-8 -*- """Subversion-related utilities.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import csv -from builtins import str -from six import StringIO # noqa - from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py index f430fd0d0..9530cebde 100644 --- a/readthedocs/vcs_support/base.py +++ b/readthedocs/vcs_support/base.py @@ -1,13 +1,9 @@ # -*- coding: utf-8 -*- """Base classes for VCS backends.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging import os import shutil -from builtins import object log = logging.getLogger(__name__) diff --git a/readthedocs/vcs_support/tests.py b/readthedocs/vcs_support/tests.py index 649f633c5..df9702ce7 100644 --- a/readthedocs/vcs_support/tests.py +++ b/readthedocs/vcs_support/tests.py @@ -1,10 +1,8 @@ -from __future__ import absolute_import import os import shutil import unittest import mock - from django.conf import settings from readthedocs.vcs_support import utils @@ -64,5 +62,3 @@ class TestNonBlockingLock(unittest.TestCase): pass except utils.LockTimeout: raise AssertionError('Should have thrown LockTimeout') - - diff --git a/readthedocs/vcs_support/utils.py b/readthedocs/vcs_support/utils.py index eb5d22bb5..567147638 100644 --- a/readthedocs/vcs_support/utils.py +++ b/readthedocs/vcs_support/utils.py @@ -1,12 +1,9 @@ """Locking utilities.""" -from __future__ import absolute_import - import errno import logging import os import stat import time -from builtins import object log = logging.getLogger(__name__) diff --git a/readthedocs/worker.py b/readthedocs/worker.py index 21023e9cd..47dfdb482 100644 --- a/readthedocs/worker.py +++ b/readthedocs/worker.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """Celery worker application instantiation""" -from __future__ import absolute_import, unicode_literals - import os from celery import Celery From c56b2893c916134b42d73e3eea94a15c6889e345 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:03:29 +0100 Subject: [PATCH 13/65] Remove six and future from requirements --- requirements/pip.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements/pip.txt b/requirements/pip.txt index 3490128e2..6d4ac7d2d 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -16,8 +16,6 @@ mkdocs==1.0.4 Markdown==3.0.1 django==1.11.16 -six==1.11.0 -future==0.17.1 django-tastypie==0.14.2 django-guardian==1.4.9 django-extensions==2.1.4 From 979f216507903b4ff4d30cf81e35fe445ed6aa68 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:07:00 +0100 Subject: [PATCH 14/65] Manually remove usage of `future` module --- readthedocs/__init__.py | 2 +- readthedocs/core/signals.py | 2 +- readthedocs/core/templatetags/core_tags.py | 2 +- readthedocs/projects/forms.py | 2 +- readthedocs/projects/migrations/0010_migrate_domain_data.py | 3 +-- readthedocs/projects/models.py | 2 +- readthedocs/projects/validators.py | 2 +- readthedocs/rtd_tests/tests/test_post_commit_hooks.py | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/readthedocs/__init__.py b/readthedocs/__init__.py index bf6f944d7..1c8afb1a0 100644 --- a/readthedocs/__init__.py +++ b/readthedocs/__init__.py @@ -3,7 +3,7 @@ import os.path -from future.moves.configparser import RawConfigParser +from configparser import RawConfigParser def get_version(setupcfg_path): diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index 875cde381..f8fa5f5ad 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -3,13 +3,13 @@ """Signal handling for core app.""" import logging +from urllib.parse import urlparse from corsheaders import signals from django.conf import settings from django.db.models import Count, Q from django.db.models.signals import pre_delete from django.dispatch import Signal, receiver -from future.backports.urllib.parse import urlparse from rest_framework.permissions import SAFE_METHODS from readthedocs.oauth.models import RemoteOrganization diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index d05831aad..e1b9e6f79 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,12 +1,12 @@ """Template tags for core app.""" import hashlib +from urllib.parse import urlparse from django import template from django.conf import settings from django.utils.encoding import force_bytes, force_text from django.utils.safestring import mark_safe -from future.backports.urllib.parse import urlencode from readthedocs import __version__ from readthedocs.core.resolver import resolve diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index a92921388..05f3afb7e 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -2,6 +2,7 @@ """Project forms.""" from random import choice +from urllib.parse import urlparse from django import forms from django.conf import settings @@ -9,7 +10,6 @@ from django.contrib.auth.models import User from django.template.loader import render_to_string from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ -from future.backports.urllib.parse import urlparse from guardian.shortcuts import assign from textclassifier.validators import ClassifierValidator diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py index 636f200bf..2d18756d6 100644 --- a/readthedocs/projects/migrations/0010_migrate_domain_data.py +++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py @@ -2,8 +2,7 @@ from __future__ import (absolute_import, print_function, unicode_literals) from django.db import models, migrations -from future.backports.urllib.parse import urlparse - +from urllib.parse import urlparse import readthedocs.projects.validators diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 24bda8f18..a54f15d87 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -4,6 +4,7 @@ import fnmatch import logging import os +from urllib.parse import urlparse from django.conf import settings from django.contrib.auth.models import User @@ -12,7 +13,6 @@ from django.urls import NoReverseMatch, reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django_extensions.db.models import TimeStampedModel -from future.backports.urllib.parse import urlparse # noqa from guardian.shortcuts import assign from taggit.managers import TaggableManager diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py index a362692c4..d0f78d0ba 100644 --- a/readthedocs/projects/validators.py +++ b/readthedocs/projects/validators.py @@ -1,13 +1,13 @@ """Validators for projects app.""" import re +from urllib.parse import urlparse from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.utils.deconstruct import deconstructible from django.utils.translation import ugettext_lazy as _ -from future.backports.urllib.parse import urlparse domain_regex = ( diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index 97eceae19..6b4a6db07 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -4,7 +4,7 @@ import logging import mock from django.test import TestCase from django_dynamic_fixture import get -from future.backports.urllib.parse import urlencode +from urllib.parse import urlparse from readthedocs.builds.models import Version from readthedocs.projects.models import Feature, Project From f124be55434d87832ba5d50a59f6d158d3e547d2 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:10:21 +0100 Subject: [PATCH 15/65] Remove skipif --- readthedocs/config/tests/test_find.py | 17 ----------------- readthedocs/rtd_tests/tests/test_backend.py | 2 -- 2 files changed, 19 deletions(-) diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index dd4715f20..048a6c9b9 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -1,7 +1,3 @@ -import os - -import pytest - from readthedocs.config.find import find_one from .utils import apply_fs @@ -17,16 +13,3 @@ def test_find_at_root(tmpdir): base = str(tmpdir) path = find_one(base, r'readthedocs\.yml') assert path == os.path.abspath(os.path.join(base, 'readthedocs.yml')) - - -@pytest.mark.skipif(not six.PY2, reason='Only for python2') -def test_find_unicode_path(tmpdir): - base_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), 'fixtures/bad_encode_project') - ) - path = find_one(base_path, r'readthedocs\.yml') - assert path == '' - unicode_base_path = base_path.decode('utf-8') - assert isinstance(unicode_base_path, unicode) - path = find_one(unicode_base_path, r'readthedocs\.yml') - assert path == '' diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index 76eb07d37..c47e8d467 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -5,7 +5,6 @@ from os.path import exists from tempfile import mkdtemp import django_dynamic_fixture as fixture -import pytest from django.contrib.auth.models import User from mock import Mock, patch @@ -72,7 +71,6 @@ class TestGitBackend(RTDTestCase): {branch.verbose_name for branch in repo.branches}, ) - @pytest.mark.skipif(six.PY2, reason='Only for python3') @patch('readthedocs.projects.models.Project.checkout_path') def test_git_branches_unicode(self, checkout_path): repo_path = self.project.repo From 05f5309d996c4f47baf69c1598e5f20aa585a029 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:11:05 +0100 Subject: [PATCH 16/65] Remove usage of `from django.utils import six` --- readthedocs/core/utils/__init__.py | 1 - readthedocs/rtd_tests/tests/test_api.py | 1 - 2 files changed, 2 deletions(-) diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py index 0b8134751..8632b1557 100644 --- a/readthedocs/core/utils/__init__.py +++ b/readthedocs/core/utils/__init__.py @@ -11,7 +11,6 @@ import os import re from django.conf import settings -from django.utils import six from django.utils.functional import allow_lazy from django.utils.safestring import SafeText, mark_safe from django.utils.text import slugify as slugify_base diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index 44f2a0374..01693da18 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -9,7 +9,6 @@ from django.contrib.auth.models import User from django.http import QueryDict from django.test import TestCase from django.urls import reverse -from django.utils import six from django_dynamic_fixture import get from rest_framework import status from rest_framework.test import APIClient From 685e547aa34f9809b68cbc720f9c68ba767cac5d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:12:29 +0100 Subject: [PATCH 17/65] Use ipaddress from standard library --- readthedocs/analytics/utils.py | 7 +- readthedocs/analytics/vendor/__init__.py | 0 readthedocs/analytics/vendor/ipaddress.py | 2418 --------------------- 3 files changed, 1 insertion(+), 2424 deletions(-) delete mode 100644 readthedocs/analytics/vendor/__init__.py delete mode 100644 readthedocs/analytics/vendor/ipaddress.py diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py index 52c44d5cd..5c74cfd1e 100644 --- a/readthedocs/analytics/utils.py +++ b/readthedocs/analytics/utils.py @@ -1,6 +1,7 @@ """Utilities related to analytics""" import hashlib +import ipaddress import logging import requests @@ -10,12 +11,6 @@ from django.utils.encoding import force_bytes, force_text from user_agents import parse -try: - # Python 3.3+ only - import ipaddress -except ImportError: - from .vendor import ipaddress - log = logging.getLogger(__name__) # noqa diff --git a/readthedocs/analytics/vendor/__init__.py b/readthedocs/analytics/vendor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/readthedocs/analytics/vendor/ipaddress.py b/readthedocs/analytics/vendor/ipaddress.py deleted file mode 100644 index 8014d2ef7..000000000 --- a/readthedocs/analytics/vendor/ipaddress.py +++ /dev/null @@ -1,2418 +0,0 @@ -# flake8: noqa -# Copyright 2007 Google Inc. -# Licensed to PSF under a Contributor Agreement. - -"""A fast, lightweight IPv4/IPv6 manipulation library in Python. - -This library is used to create/poke/manipulate IPv4 and IPv6 addresses -and networks. - -""" - -import itertools -import struct - - -__version__ = '1.0.22' - -# Compatibility functions -_compat_int_types = (int,) -try: - _compat_int_types = (int, long) -except NameError: - pass -try: - _compat_str = unicode -except NameError: - _compat_str = str - assert bytes != str -if b'\0'[0] == 0: # Python 3 semantics - def _compat_bytes_to_byte_vals(byt): - return byt -else: - def _compat_bytes_to_byte_vals(byt): - return [struct.unpack(b'!B', b)[0] for b in byt] -try: - _compat_int_from_byte_vals = int.from_bytes -except AttributeError: - def _compat_int_from_byte_vals(bytvals, endianess): - assert endianess == 'big' - res = 0 - for bv in bytvals: - assert isinstance(bv, _compat_int_types) - res = (res << 8) + bv - return res - - -def _compat_to_bytes(intval, length, endianess): - assert isinstance(intval, _compat_int_types) - assert endianess == 'big' - if length == 4: - if intval < 0 or intval >= 2 ** 32: - raise struct.error("integer out of range for 'I' format code") - return struct.pack(b'!I', intval) - elif length == 16: - if intval < 0 or intval >= 2 ** 128: - raise struct.error("integer out of range for 'QQ' format code") - return struct.pack(b'!QQ', intval >> 64, intval & 0xffffffffffffffff) - else: - raise NotImplementedError() - - -if hasattr(int, 'bit_length'): - # Not int.bit_length , since that won't work in 2.7 where long exists - def _compat_bit_length(i): - return i.bit_length() -else: - def _compat_bit_length(i): - for res in itertools.count(): - if i >> res == 0: - return res - - -def _compat_range(start, end, step=1): - assert step > 0 - i = start - while i < end: - yield i - i += step - - -class _TotalOrderingMixin(object): - __slots__ = () - - # Helper that derives the other comparison operations from - # __lt__ and __eq__ - # We avoid functools.total_ordering because it doesn't handle - # NotImplemented correctly yet (http://bugs.python.org/issue10042) - def __eq__(self, other): - raise NotImplementedError - - def __ne__(self, other): - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not equal - - def __lt__(self, other): - raise NotImplementedError - - def __le__(self, other): - less = self.__lt__(other) - if less is NotImplemented or not less: - return self.__eq__(other) - return less - - def __gt__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not (less or equal) - - def __ge__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - return not less - - -IPV4LENGTH = 32 -IPV6LENGTH = 128 - - -class AddressValueError(ValueError): - """A Value Error related to the address.""" - - -class NetmaskValueError(ValueError): - """A Value Error related to the netmask.""" - - -def ip_address(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Address or IPv6Address object. - - Raises: - ValueError: if the *address* passed isn't either a v4 or a v6 - address - - """ - try: - return IPv4Address(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Address(address) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % - address) - - -def ip_network(address, strict=True): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP network. Either IPv4 or - IPv6 networks may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Network or IPv6Network object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. Or if the network has host bits set. - - """ - try: - return IPv4Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 network. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % - address) - - -def ip_interface(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Interface or IPv6Interface object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. - - Notes: - The IPv?Interface classes describe an Address on a particular - Network, so they're basically a combination of both the Address - and Network classes. - - """ - try: - return IPv4Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' % - address) - - -def v4_int_to_packed(address): - """Represent an address as 4 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv4 IP address. - - Returns: - The integer address packed as 4 bytes in network (big-endian) order. - - Raises: - ValueError: If the integer is negative or too large to be an - IPv4 IP address. - - """ - try: - return _compat_to_bytes(address, 4, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv4") - - -def v6_int_to_packed(address): - """Represent an address as 16 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv6 IP address. - - Returns: - The integer address packed as 16 bytes in network (big-endian) order. - - """ - try: - return _compat_to_bytes(address, 16, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv6") - - -def _split_optional_netmask(address): - """Helper to split the netmask and raise AddressValueError if needed""" - addr = _compat_str(address).split('/') - if len(addr) > 2: - raise AddressValueError("Only one '/' permitted in %r" % address) - return addr - - -def _find_address_range(addresses): - """Find a sequence of sorted deduplicated IPv#Address. - - Args: - addresses: a list of IPv#Address objects. - - Yields: - A tuple containing the first and last IP addresses in the sequence. - - """ - it = iter(addresses) - first = last = next(it) - for ip in it: - if ip._ip != last._ip + 1: - yield first, last - first = ip - last = ip - yield first, last - - -def _count_righthand_zero_bits(number, bits): - """Count the number of zero bits on the right hand side. - - Args: - number: an integer. - bits: maximum number of bits to count. - - Returns: - The number of zero bits on the right hand side of the number. - - """ - if number == 0: - return bits - return min(bits, _compat_bit_length(~number & (number - 1))) - - -def summarize_address_range(first, last): - """Summarize a network range given the first and last IP addresses. - - Example: - >>> list(summarize_address_range(IPv4Address('192.0.2.0'), - ... IPv4Address('192.0.2.130'))) - ... #doctest: +NORMALIZE_WHITESPACE - [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), - IPv4Network('192.0.2.130/32')] - - Args: - first: the first IPv4Address or IPv6Address in the range. - last: the last IPv4Address or IPv6Address in the range. - - Returns: - An iterator of the summarized IPv(4|6) network objects. - - Raise: - TypeError: - If the first and last objects are not IP addresses. - If the first and last objects are not the same version. - ValueError: - If the last object is not greater than the first. - If the version of the first address is not 4 or 6. - - """ - if (not (isinstance(first, _BaseAddress) and - isinstance(last, _BaseAddress))): - raise TypeError('first and last must be IP addresses, not networks') - if first.version != last.version: - raise TypeError("%s and %s are not of the same version" % ( - first, last)) - if first > last: - raise ValueError('last IP address must be greater than first') - - if first.version == 4: - ip = IPv4Network - elif first.version == 6: - ip = IPv6Network - else: - raise ValueError('unknown IP version') - - ip_bits = first._max_prefixlen - first_int = first._ip - last_int = last._ip - while first_int <= last_int: - nbits = min(_count_righthand_zero_bits(first_int, ip_bits), - _compat_bit_length(last_int - first_int + 1) - 1) - net = ip((first_int, ip_bits - nbits)) - yield net - first_int += 1 << nbits - if first_int - 1 == ip._ALL_ONES: - break - - -def _collapse_addresses_internal(addresses): - """Loops through the addresses, collapsing concurrent netblocks. - - Example: - - ip1 = IPv4Network('192.0.2.0/26') - ip2 = IPv4Network('192.0.2.64/26') - ip3 = IPv4Network('192.0.2.128/26') - ip4 = IPv4Network('192.0.2.192/26') - - _collapse_addresses_internal([ip1, ip2, ip3, ip4]) -> - [IPv4Network('192.0.2.0/24')] - - This shouldn't be called directly; it is called via - collapse_addresses([]). - - Args: - addresses: A list of IPv4Network's or IPv6Network's - - Returns: - A list of IPv4Network's or IPv6Network's depending on what we were - passed. - - """ - # First merge - to_merge = list(addresses) - subnets = {} - while to_merge: - net = to_merge.pop() - supernet = net.supernet() - existing = subnets.get(supernet) - if existing is None: - subnets[supernet] = net - elif existing != net: - # Merge consecutive subnets - del subnets[supernet] - to_merge.append(supernet) - # Then iterate over resulting networks, skipping subsumed subnets - last = None - for net in sorted(subnets.values()): - if last is not None: - # Since they are sorted, - # last.network_address <= net.network_address is a given. - if last.broadcast_address >= net.broadcast_address: - continue - yield net - last = net - - -def collapse_addresses(addresses): - """Collapse a list of IP objects. - - Example: - collapse_addresses([IPv4Network('192.0.2.0/25'), - IPv4Network('192.0.2.128/25')]) -> - [IPv4Network('192.0.2.0/24')] - - Args: - addresses: An iterator of IPv4Network or IPv6Network objects. - - Returns: - An iterator of the collapsed IPv(4|6)Network objects. - - Raises: - TypeError: If passed a list of mixed version objects. - - """ - addrs = [] - ips = [] - nets = [] - - # split IP addresses and networks - for ip in addresses: - if isinstance(ip, _BaseAddress): - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - ips.append(ip) - elif ip._prefixlen == ip._max_prefixlen: - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - try: - ips.append(ip.ip) - except AttributeError: - ips.append(ip.network_address) - else: - if nets and nets[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, nets[-1])) - nets.append(ip) - - # sort and dedup - ips = sorted(set(ips)) - - # find consecutive address ranges in the sorted sequence and summarize them - if ips: - for first, last in _find_address_range(ips): - addrs.extend(summarize_address_range(first, last)) - - return _collapse_addresses_internal(addrs + nets) - - -def get_mixed_type_key(obj): - """Return a key suitable for sorting between networks and addresses. - - Address and Network objects are not sortable by default; they're - fundamentally different so the expression - - IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24') - - doesn't make any sense. There are some times however, where you may wish - to have ipaddress sort these for you anyway. If you need to do this, you - can use this function as the key= argument to sorted(). - - Args: - obj: either a Network or Address object. - Returns: - appropriate key. - - """ - if isinstance(obj, _BaseNetwork): - return obj._get_networks_key() - elif isinstance(obj, _BaseAddress): - return obj._get_address_key() - return NotImplemented - - -class _IPAddressBase(_TotalOrderingMixin): - - """The mother class.""" - - __slots__ = () - - @property - def exploded(self): - """Return the longhand version of the IP address as a string.""" - return self._explode_shorthand_ip_string() - - @property - def compressed(self): - """Return the shorthand version of the IP address as a string.""" - return _compat_str(self) - - @property - def reverse_pointer(self): - """The name of the reverse DNS pointer for the IP address, e.g.: - >>> ipaddress.ip_address("127.0.0.1").reverse_pointer - '1.0.0.127.in-addr.arpa' - >>> ipaddress.ip_address("2001:db8::1").reverse_pointer - '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' - - """ - return self._reverse_pointer() - - @property - def version(self): - msg = '%200s has no version specified' % (type(self),) - raise NotImplementedError(msg) - - def _check_int_address(self, address): - if address < 0: - msg = "%d (< 0) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._version)) - if address > self._ALL_ONES: - msg = "%d (>= 2**%d) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._max_prefixlen, - self._version)) - - def _check_packed_address(self, address, expected_len): - address_len = len(address) - if address_len != expected_len: - msg = ( - '%r (len %d != %d) is not permitted as an IPv%d address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?') - raise AddressValueError(msg % (address, address_len, - expected_len, self._version)) - - @classmethod - def _ip_int_from_prefix(cls, prefixlen): - """Turn the prefix length into a bitwise netmask - - Args: - prefixlen: An integer, the prefix length. - - Returns: - An integer. - - """ - return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen) - - @classmethod - def _prefix_from_ip_int(cls, ip_int): - """Return prefix length from the bitwise netmask. - - Args: - ip_int: An integer, the netmask in expanded bitwise format - - Returns: - An integer, the prefix length. - - Raises: - ValueError: If the input intermingles zeroes & ones - """ - trailing_zeroes = _count_righthand_zero_bits(ip_int, - cls._max_prefixlen) - prefixlen = cls._max_prefixlen - trailing_zeroes - leading_ones = ip_int >> trailing_zeroes - all_ones = (1 << prefixlen) - 1 - if leading_ones != all_ones: - byteslen = cls._max_prefixlen // 8 - details = _compat_to_bytes(ip_int, byteslen, 'big') - msg = 'Netmask pattern %r mixes zeroes & ones' - raise ValueError(msg % details) - return prefixlen - - @classmethod - def _report_invalid_netmask(cls, netmask_str): - msg = '%r is not a valid netmask' % netmask_str - raise NetmaskValueError(msg) - - @classmethod - def _prefix_from_prefix_string(cls, prefixlen_str): - """Return prefix length from a numeric string - - Args: - prefixlen_str: The string to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask - """ - # int allows a leading +/- as well as surrounding whitespace, - # so we ensure that isn't the case - if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str): - cls._report_invalid_netmask(prefixlen_str) - try: - prefixlen = int(prefixlen_str) - except ValueError: - cls._report_invalid_netmask(prefixlen_str) - if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen_str) - return prefixlen - - @classmethod - def _prefix_from_ip_string(cls, ip_str): - """Turn a netmask/hostmask string into a prefix length - - Args: - ip_str: The netmask/hostmask to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask/hostmask - """ - # Parse the netmask/hostmask like an IP address. - try: - ip_int = cls._ip_int_from_string(ip_str) - except AddressValueError: - cls._report_invalid_netmask(ip_str) - - # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). - # Note that the two ambiguous cases (all-ones and all-zeroes) are - # treated as netmasks. - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - pass - - # Invert the bits, and try matching a /0+1+/ hostmask instead. - ip_int ^= cls._ALL_ONES - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - cls._report_invalid_netmask(ip_str) - - def __reduce__(self): - return self.__class__, (_compat_str(self),) - - -class _BaseAddress(_IPAddressBase): - - """A generic IP object. - - This IP class contains the version independent methods which are - used by single IP addresses. - """ - - __slots__ = () - - def __int__(self): - return self._ip - - def __eq__(self, other): - try: - return (self._ip == other._ip and - self._version == other._version) - except AttributeError: - return NotImplemented - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseAddress): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self._ip != other._ip: - return self._ip < other._ip - return False - - # Shorthand for Integer addition and subtraction. This is not - # meant to ever support addition/subtraction of addresses. - def __add__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) + other) - - def __sub__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) - other) - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return _compat_str(self._string_from_ip_int(self._ip)) - - def __hash__(self): - return hash(hex(int(self._ip))) - - def _get_address_key(self): - return (self._version, self) - - def __reduce__(self): - return self.__class__, (self._ip,) - - -class _BaseNetwork(_IPAddressBase): - - """A generic IP network object. - - This IP class contains the version independent methods which are - used by networks. - - """ - def __init__(self, address): - self._cache = {} - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return '%s/%d' % (self.network_address, self.prefixlen) - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the network - or broadcast addresses. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast): - yield self._address_class(x) - - def __iter__(self): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network, broadcast + 1): - yield self._address_class(x) - - def __getitem__(self, n): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - if n >= 0: - if network + n > broadcast: - raise IndexError('address out of range') - return self._address_class(network + n) - else: - n += 1 - if broadcast + n < network: - raise IndexError('address out of range') - return self._address_class(broadcast + n) - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseNetwork): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self.network_address != other.network_address: - return self.network_address < other.network_address - if self.netmask != other.netmask: - return self.netmask < other.netmask - return False - - def __eq__(self, other): - try: - return (self._version == other._version and - self.network_address == other.network_address and - int(self.netmask) == int(other.netmask)) - except AttributeError: - return NotImplemented - - def __hash__(self): - return hash(int(self.network_address) ^ int(self.netmask)) - - def __contains__(self, other): - # always false if one is v4 and the other is v6. - if self._version != other._version: - return False - # dealing with another network. - if isinstance(other, _BaseNetwork): - return False - # dealing with another address - else: - # address - return (int(self.network_address) <= int(other._ip) <= - int(self.broadcast_address)) - - def overlaps(self, other): - """Tell if self is partly contained in other.""" - return self.network_address in other or ( - self.broadcast_address in other or ( - other.network_address in self or ( - other.broadcast_address in self))) - - @property - def broadcast_address(self): - x = self._cache.get('broadcast_address') - if x is None: - x = self._address_class(int(self.network_address) | - int(self.hostmask)) - self._cache['broadcast_address'] = x - return x - - @property - def hostmask(self): - x = self._cache.get('hostmask') - if x is None: - x = self._address_class(int(self.netmask) ^ self._ALL_ONES) - self._cache['hostmask'] = x - return x - - @property - def with_prefixlen(self): - return '%s/%d' % (self.network_address, self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self.network_address, self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self.network_address, self.hostmask) - - @property - def num_addresses(self): - """Number of hosts in the current subnet.""" - return int(self.broadcast_address) - int(self.network_address) + 1 - - @property - def _address_class(self): - # Returning bare address objects (rather than interfaces) allows for - # more consistent behaviour across the network address, broadcast - # address and individual host addresses. - msg = '%200s has no associated address class' % (type(self),) - raise NotImplementedError(msg) - - @property - def prefixlen(self): - return self._prefixlen - - def address_exclude(self, other): - """Remove an address from a larger block. - - For example: - - addr1 = ip_network('192.0.2.0/28') - addr2 = ip_network('192.0.2.1/32') - list(addr1.address_exclude(addr2)) = - [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'), - IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')] - - or IPv6: - - addr1 = ip_network('2001:db8::1/32') - addr2 = ip_network('2001:db8::1/128') - list(addr1.address_exclude(addr2)) = - [ip_network('2001:db8::1/128'), - ip_network('2001:db8::2/127'), - ip_network('2001:db8::4/126'), - ip_network('2001:db8::8/125'), - ... - ip_network('2001:db8:8000::/33')] - - Args: - other: An IPv4Network or IPv6Network object of the same type. - - Returns: - An iterator of the IPv(4|6)Network objects which is self - minus other. - - Raises: - TypeError: If self and other are of differing address - versions, or if other is not a network object. - ValueError: If other is not completely contained by self. - - """ - if not self._version == other._version: - raise TypeError("%s and %s are not of the same version" % ( - self, other)) - - if not isinstance(other, _BaseNetwork): - raise TypeError("%s is not a network object" % other) - - if not other.subnet_of(self): - raise ValueError('%s not contained in %s' % (other, self)) - if other == self: - return - - # Make sure we're comparing the network of other. - other = other.__class__('%s/%s' % (other.network_address, - other.prefixlen)) - - s1, s2 = self.subnets() - while s1 != other and s2 != other: - if other.subnet_of(s1): - yield s2 - s1, s2 = s1.subnets() - elif other.subnet_of(s2): - yield s1 - s1, s2 = s2.subnets() - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - if s1 == other: - yield s2 - elif s2 == other: - yield s1 - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - - def compare_networks(self, other): - """Compare two IP objects. - - This is only concerned about the comparison of the integer - representation of the network addresses. This means that the - host bits aren't considered at all in this method. If you want - to compare host bits, you can easily enough do a - 'HostA._ip < HostB._ip' - - Args: - other: An IP object. - - Returns: - If the IP versions of self and other are the same, returns: - - -1 if self < other: - eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25') - IPv6Network('2001:db8::1000/124') < - IPv6Network('2001:db8::2000/124') - 0 if self == other - eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24') - IPv6Network('2001:db8::1000/124') == - IPv6Network('2001:db8::1000/124') - 1 if self > other - eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25') - IPv6Network('2001:db8::2000/124') > - IPv6Network('2001:db8::1000/124') - - Raises: - TypeError if the IP versions are different. - - """ - # does this need to raise a ValueError? - if self._version != other._version: - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - # self._version == other._version below here: - if self.network_address < other.network_address: - return -1 - if self.network_address > other.network_address: - return 1 - # self.network_address == other.network_address below here: - if self.netmask < other.netmask: - return -1 - if self.netmask > other.netmask: - return 1 - return 0 - - def _get_networks_key(self): - """Network-only key function. - - Returns an object that identifies this address' network and - netmask. This function is a suitable "key" argument for sorted() - and list.sort(). - - """ - return (self._version, self.network_address, self.netmask) - - def subnets(self, prefixlen_diff=1, new_prefix=None): - """The subnets which join to make the current subnet. - - In the case that self contains only one IP - (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 - for IPv6), yield an iterator with just ourself. - - Args: - prefixlen_diff: An integer, the amount the prefix length - should be increased by. This should not be set if - new_prefix is also set. - new_prefix: The desired new prefix length. This must be a - larger number (smaller prefix) than the existing prefix. - This should not be set if prefixlen_diff is also set. - - Returns: - An iterator of IPv(4|6) objects. - - Raises: - ValueError: The prefixlen_diff is too small or too large. - OR - prefixlen_diff and new_prefix are both set or new_prefix - is a smaller number than the current prefix (smaller - number means a larger network) - - """ - if self._prefixlen == self._max_prefixlen: - yield self - return - - if new_prefix is not None: - if new_prefix < self._prefixlen: - raise ValueError('new prefix must be longer') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = new_prefix - self._prefixlen - - if prefixlen_diff < 0: - raise ValueError('prefix length diff must be > 0') - new_prefixlen = self._prefixlen + prefixlen_diff - - if new_prefixlen > self._max_prefixlen: - raise ValueError( - 'prefix length diff %d is invalid for netblock %s' % ( - new_prefixlen, self)) - - start = int(self.network_address) - end = int(self.broadcast_address) + 1 - step = (int(self.hostmask) + 1) >> prefixlen_diff - for new_addr in _compat_range(start, end, step): - current = self.__class__((new_addr, new_prefixlen)) - yield current - - def supernet(self, prefixlen_diff=1, new_prefix=None): - """The supernet containing the current network. - - Args: - prefixlen_diff: An integer, the amount the prefix length of - the network should be decreased by. For example, given a - /24 network and a prefixlen_diff of 3, a supernet with a - /21 netmask is returned. - - Returns: - An IPv4 network object. - - Raises: - ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have - a negative prefix length. - OR - If prefixlen_diff and new_prefix are both set or new_prefix is a - larger number than the current prefix (larger number means a - smaller network) - - """ - if self._prefixlen == 0: - return self - - if new_prefix is not None: - if new_prefix > self._prefixlen: - raise ValueError('new prefix must be shorter') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = self._prefixlen - new_prefix - - new_prefixlen = self.prefixlen - prefixlen_diff - if new_prefixlen < 0: - raise ValueError( - 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % - (self.prefixlen, prefixlen_diff)) - return self.__class__(( - int(self.network_address) & (int(self.netmask) << prefixlen_diff), - new_prefixlen)) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return (self.network_address.is_multicast and - self.broadcast_address.is_multicast) - - @staticmethod - def _is_subnet_of(a, b): - try: - # Always false if one is v4 and the other is v6. - if a._version != b._version: - raise TypeError("%s and %s are not of the same version" (a, b)) - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) - except AttributeError: - raise TypeError("Unable to test subnet containment " - "between %s and %s" % (a, b)) - - def subnet_of(self, other): - """Return True if this network is a subnet of other.""" - return self._is_subnet_of(self, other) - - def supernet_of(self, other): - """Return True if this network is a supernet of other.""" - return self._is_subnet_of(other, self) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return (self.network_address.is_reserved and - self.broadcast_address.is_reserved) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return (self.network_address.is_link_local and - self.broadcast_address.is_link_local) - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return (self.network_address.is_private and - self.broadcast_address.is_private) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return (self.network_address.is_unspecified and - self.broadcast_address.is_unspecified) - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return (self.network_address.is_loopback and - self.broadcast_address.is_loopback) - - -class _BaseV4(object): - - """Base IPv4 object. - - The following methods are used by IPv4 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 4 - # Equivalent to 255.255.255.255 or 32 bits of 1's. - _ALL_ONES = (2 ** IPV4LENGTH) - 1 - _DECIMAL_DIGITS = frozenset('0123456789') - - # the valid octets for host and netmasks. only useful for IPv4. - _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0]) - - _max_prefixlen = IPV4LENGTH - # There are only a handful of valid v4 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - def _explode_shorthand_ip_string(self): - return _compat_str(self) - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - try: - # Check for a netmask in prefix length form - prefixlen = cls._prefix_from_prefix_string(arg) - except NetmaskValueError: - # Check for a netmask or hostmask in dotted-quad form. - # This may raise NetmaskValueError. - prefixlen = cls._prefix_from_ip_string(arg) - netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn the given IP string into an integer for comparison. - - Args: - ip_str: A string, the IP ip_str. - - Returns: - The IP ip_str as an integer. - - Raises: - AddressValueError: if ip_str isn't a valid IPv4 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - octets = ip_str.split('.') - if len(octets) != 4: - raise AddressValueError("Expected 4 octets in %r" % ip_str) - - try: - return _compat_int_from_byte_vals( - map(cls._parse_octet, octets), 'big') - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_octet(cls, octet_str): - """Convert a decimal octet into an integer. - - Args: - octet_str: A string, the number to parse. - - Returns: - The octet as an integer. - - Raises: - ValueError: if the octet isn't strictly a decimal from [0..255]. - - """ - if not octet_str: - raise ValueError("Empty octet not permitted") - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._DECIMAL_DIGITS.issuperset(octet_str): - msg = "Only decimal digits permitted in %r" - raise ValueError(msg % octet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(octet_str) > 3: - msg = "At most 3 characters permitted in %r" - raise ValueError(msg % octet_str) - # Convert to integer (we know digits are legal) - octet_int = int(octet_str, 10) - # Any octets that look like they *might* be written in octal, - # and which don't look exactly the same in both octal and - # decimal are rejected as ambiguous - if octet_int > 7 and octet_str[0] == '0': - msg = "Ambiguous (octal/decimal) value in %r not permitted" - raise ValueError(msg % octet_str) - if octet_int > 255: - raise ValueError("Octet %d (> 255) not permitted" % octet_int) - return octet_int - - @classmethod - def _string_from_ip_int(cls, ip_int): - """Turns a 32-bit integer into dotted decimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - The IP address as a string in dotted decimal notation. - - """ - return '.'.join(_compat_str(struct.unpack(b'!B', b)[0] - if isinstance(b, bytes) - else b) - for b in _compat_to_bytes(ip_int, 4, 'big')) - - def _is_hostmask(self, ip_str): - """Test if the IP string is a hostmask (rather than a netmask). - - Args: - ip_str: A string, the potential hostmask. - - Returns: - A boolean, True if the IP string is a hostmask. - - """ - bits = ip_str.split('.') - try: - parts = [x for x in map(int, bits) if x in self._valid_mask_octets] - except ValueError: - return False - if len(parts) != len(bits): - return False - if parts[0] < parts[-1]: - return True - return False - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv4 address. - - This implements the method described in RFC1035 3.5. - - """ - reverse_octets = _compat_str(self).split('.')[::-1] - return '.'.join(reverse_octets) + '.in-addr.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv4Address(_BaseV4, _BaseAddress): - - """Represent and manipulate single IPv4 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - - """ - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv4Address('192.0.2.1') == IPv4Address(3221225985). - or, more generally - IPv4Address(int(IPv4Address('192.0.2.1'))) == - IPv4Address('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 4) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v4_int_to_packed(self._ip) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within the - reserved IPv4 Network range. - - """ - return self in self._constants._reserved_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - return ( - self not in self._constants._public_network and - not self.is_private) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is multicast. - See RFC 3171 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 5735 3. - - """ - return self == self._constants._unspecified_address - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback per RFC 3330. - - """ - return self in self._constants._loopback_network - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is link-local per RFC 3927. - - """ - return self in self._constants._linklocal_network - - -class IPv4Interface(IPv4Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv4Address.__init__(self, address) - self.network = IPv4Network(self._ip) - self._prefixlen = self._max_prefixlen - return - - if isinstance(address, tuple): - IPv4Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - - self.network = IPv4Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv4Address.__init__(self, addr[0]) - - self.network = IPv4Network(address, strict=False) - self._prefixlen = self.network._prefixlen - - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv4Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv4Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv4Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - -class IPv4Network(_BaseV4, _BaseNetwork): - - """This class represents and manipulates 32-bit IPv4 network + addresses.. - - Attributes: [examples for IPv4Network('192.0.2.0/27')] - .network_address: IPv4Address('192.0.2.0') - .hostmask: IPv4Address('0.0.0.31') - .broadcast_address: IPv4Address('192.0.2.32') - .netmask: IPv4Address('255.255.255.224') - .prefixlen: 27 - - """ - # Class to use when creating address objects - _address_class = IPv4Address - - def __init__(self, address, strict=True): - - """Instantiate a new IPv4 network object. - - Args: - address: A string or integer representing the IP [& network]. - '192.0.2.0/24' - '192.0.2.0/255.255.255.0' - '192.0.0.2/0.0.0.255' - are all functionally the same in IPv4. Similarly, - '192.0.2.1' - '192.0.2.1/255.255.255.255' - '192.0.2.1/32' - are also functionally equivalent. That is to say, failing to - provide a subnetmask will create an object with a mask of /32. - - If the mask (portion after the / in the argument) is given in - dotted quad form, it is treated as a netmask if it starts with a - non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it - starts with a zero field (e.g. 0.255.255.255 == /8), with the - single exception of an all-zero mask which is treated as a - netmask == /0. If no mask is given, a default of /32 is used. - - Additionally, an integer can be passed, so - IPv4Network('192.0.2.1') == IPv4Network(3221225985) - or, more generally - IPv4Interface(int(IPv4Interface('192.0.2.1'))) == - IPv4Interface('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - NetmaskValueError: If the netmask isn't valid for - an IPv4 address. - ValueError: If strict is True and a network address is not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Constructing from a packed address or integer - if isinstance(address, (_compat_int_types, bytes)): - self.network_address = IPv4Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - # fixme: address/network test here. - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - # We weren't given an address[1] - arg = self._max_prefixlen - self.network_address = IPv4Address(address[0]) - self.netmask, self._prefixlen = self._make_netmask(arg) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv4Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv4Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv4Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry. - - """ - return (not (self.network_address in IPv4Network('100.64.0.0/10') and - self.broadcast_address in IPv4Network('100.64.0.0/10')) and - not self.is_private) - - -class _IPv4Constants(object): - - _linklocal_network = IPv4Network('169.254.0.0/16') - - _loopback_network = IPv4Network('127.0.0.0/8') - - _multicast_network = IPv4Network('224.0.0.0/4') - - _public_network = IPv4Network('100.64.0.0/10') - - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), - IPv4Network('192.0.0.0/29'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), - IPv4Network('198.18.0.0/15'), - IPv4Network('198.51.100.0/24'), - IPv4Network('203.0.113.0/24'), - IPv4Network('240.0.0.0/4'), - IPv4Network('255.255.255.255/32'), - ] - - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') - - -IPv4Address._constants = _IPv4Constants - - -class _BaseV6(object): - - """Base IPv6 object. - - The following methods are used by IPv6 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 6 - _ALL_ONES = (2 ** IPV6LENGTH) - 1 - _HEXTET_COUNT = 8 - _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') - _max_prefixlen = IPV6LENGTH - - # There are only a bunch of valid v6 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - prefixlen = cls._prefix_from_prefix_string(arg) - netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn an IPv6 ip_str into an integer. - - Args: - ip_str: A string, the IPv6 ip_str. - - Returns: - An int, the IPv6 address - - Raises: - AddressValueError: if ip_str isn't a valid IPv6 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - parts = ip_str.split(':') - - # An IPv6 address needs at least 2 colons (3 parts). - _min_parts = 3 - if len(parts) < _min_parts: - msg = "At least %d parts expected in %r" % (_min_parts, ip_str) - raise AddressValueError(msg) - - # If the address has an IPv4-style suffix, convert it to hexadecimal. - if '.' in parts[-1]: - try: - ipv4_int = IPv4Address(parts.pop())._ip - except AddressValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) - parts.append('%x' % (ipv4_int & 0xFFFF)) - - # An IPv6 address can't have more than 8 colons (9 parts). - # The extra colon comes from using the "::" notation for a single - # leading or trailing zero part. - _max_parts = cls._HEXTET_COUNT + 1 - if len(parts) > _max_parts: - msg = "At most %d colons permitted in %r" % ( - _max_parts - 1, ip_str) - raise AddressValueError(msg) - - # Disregarding the endpoints, find '::' with nothing in between. - # This indicates that a run of zeroes has been skipped. - skip_index = None - for i in _compat_range(1, len(parts) - 1): - if not parts[i]: - if skip_index is not None: - # Can't have more than one '::' - msg = "At most one '::' permitted in %r" % ip_str - raise AddressValueError(msg) - skip_index = i - - # parts_hi is the number of parts to copy from above/before the '::' - # parts_lo is the number of parts to copy from below/after the '::' - if skip_index is not None: - # If we found a '::', then check if it also covers the endpoints. - parts_hi = skip_index - parts_lo = len(parts) - skip_index - 1 - if not parts[0]: - parts_hi -= 1 - if parts_hi: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - parts_lo -= 1 - if parts_lo: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo) - if parts_skipped < 1: - msg = "Expected at most %d other parts with '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str)) - else: - # Otherwise, allocate the entire address to parts_hi. The - # endpoints could still be empty, but _parse_hextet() will check - # for that. - if len(parts) != cls._HEXTET_COUNT: - msg = "Exactly %d parts expected without '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str)) - if not parts[0]: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_hi = len(parts) - parts_lo = 0 - parts_skipped = 0 - - try: - # Now, parse the hextets into a 128-bit integer. - ip_int = 0 - for i in range(parts_hi): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - ip_int <<= 16 * parts_skipped - for i in range(-parts_lo, 0): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - return ip_int - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_hextet(cls, hextet_str): - """Convert an IPv6 hextet string into an integer. - - Args: - hextet_str: A string, the number to parse. - - Returns: - The hextet as an integer. - - Raises: - ValueError: if the input isn't strictly a hex number from - [0..FFFF]. - - """ - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._HEX_DIGITS.issuperset(hextet_str): - raise ValueError("Only hex digits permitted in %r" % hextet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(hextet_str) > 4: - msg = "At most 4 characters permitted in %r" - raise ValueError(msg % hextet_str) - # Length check means we can skip checking the integer value - return int(hextet_str, 16) - - @classmethod - def _compress_hextets(cls, hextets): - """Compresses a list of hextets. - - Compresses a list of strings, replacing the longest continuous - sequence of "0" in the list with "" and adding empty strings at - the beginning or at the end of the string such that subsequently - calling ":".join(hextets) will produce the compressed version of - the IPv6 address. - - Args: - hextets: A list of strings, the hextets to compress. - - Returns: - A list of strings. - - """ - best_doublecolon_start = -1 - best_doublecolon_len = 0 - doublecolon_start = -1 - doublecolon_len = 0 - for index, hextet in enumerate(hextets): - if hextet == '0': - doublecolon_len += 1 - if doublecolon_start == -1: - # Start of a sequence of zeros. - doublecolon_start = index - if doublecolon_len > best_doublecolon_len: - # This is the longest sequence of zeros so far. - best_doublecolon_len = doublecolon_len - best_doublecolon_start = doublecolon_start - else: - doublecolon_len = 0 - doublecolon_start = -1 - - if best_doublecolon_len > 1: - best_doublecolon_end = (best_doublecolon_start + - best_doublecolon_len) - # For zeros at the end of the address. - if best_doublecolon_end == len(hextets): - hextets += [''] - hextets[best_doublecolon_start:best_doublecolon_end] = [''] - # For zeros at the beginning of the address. - if best_doublecolon_start == 0: - hextets = [''] + hextets - - return hextets - - @classmethod - def _string_from_ip_int(cls, ip_int=None): - """Turns a 128-bit integer into hexadecimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - A string, the hexadecimal representation of the address. - - Raises: - ValueError: The address is bigger than 128 bits of all ones. - - """ - if ip_int is None: - ip_int = int(cls._ip) - - if ip_int > cls._ALL_ONES: - raise ValueError('IPv6 address is too large') - - hex_str = '%032x' % ip_int - hextets = ['%x' % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)] - - hextets = cls._compress_hextets(hextets) - return ':'.join(hextets) - - def _explode_shorthand_ip_string(self): - """Expand a shortened IPv6 address. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A string, the expanded IPv6 address. - - """ - if isinstance(self, IPv6Network): - ip_str = _compat_str(self.network_address) - elif isinstance(self, IPv6Interface): - ip_str = _compat_str(self.ip) - else: - ip_str = _compat_str(self) - - ip_int = self._ip_int_from_string(ip_str) - hex_str = '%032x' % ip_int - parts = [hex_str[x:x + 4] for x in range(0, 32, 4)] - if isinstance(self, (_BaseNetwork, IPv6Interface)): - return '%s/%d' % (':'.join(parts), self._prefixlen) - return ':'.join(parts) - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv6 address. - - This implements the method described in RFC3596 2.5. - - """ - reverse_chars = self.exploded[::-1].replace(':', '') - return '.'.join(reverse_chars) + '.ip6.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv6Address(_BaseV6, _BaseAddress): - - """Represent and manipulate single IPv6 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - """Instantiate a new IPv6 address object. - - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv6Address('2001:db8::') == - IPv6Address(42540766411282592856903984951653826560) - or, more generally - IPv6Address(int(IPv6Address('2001:db8::'))) == - IPv6Address('2001:db8::') - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 16) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v6_int_to_packed(self._ip) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return any(self in x for x in self._constants._reserved_networks) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return self in self._constants._linklocal_network - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return self in self._constants._sitelocal_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv6-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, true if the address is not reserved per - iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return self._ip == 0 - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return self._ip == 1 - - @property - def ipv4_mapped(self): - """Return the IPv4 mapped address. - - Returns: - If the IPv6 address is a v4 mapped address, return the - IPv4 mapped address. Return None otherwise. - - """ - if (self._ip >> 32) != 0xFFFF: - return None - return IPv4Address(self._ip & 0xFFFFFFFF) - - @property - def teredo(self): - """Tuple of embedded teredo IPs. - - Returns: - Tuple of the (server, client) IPs or None if the address - doesn't appear to be a teredo address (doesn't start with - 2001::/32) - - """ - if (self._ip >> 96) != 0x20010000: - return None - return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), - IPv4Address(~self._ip & 0xFFFFFFFF)) - - @property - def sixtofour(self): - """Return the IPv4 6to4 embedded address. - - Returns: - The IPv4 6to4-embedded address if present or None if the - address doesn't appear to contain a 6to4 embedded address. - - """ - if (self._ip >> 112) != 0x2002: - return None - return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) - - -class IPv6Interface(IPv6Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv6Address.__init__(self, address) - self.network = IPv6Network(self._ip) - self._prefixlen = self._max_prefixlen - return - if isinstance(address, tuple): - IPv6Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv6Address.__init__(self, addr[0]) - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self._prefixlen = self.network._prefixlen - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv6Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv6Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv6Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - @property - def is_unspecified(self): - return self._ip == 0 and self.network.is_unspecified - - @property - def is_loopback(self): - return self._ip == 1 and self.network.is_loopback - - -class IPv6Network(_BaseV6, _BaseNetwork): - - """This class represents and manipulates 128-bit IPv6 networks. - - Attributes: [examples for IPv6('2001:db8::1000/124')] - .network_address: IPv6Address('2001:db8::1000') - .hostmask: IPv6Address('::f') - .broadcast_address: IPv6Address('2001:db8::100f') - .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') - .prefixlen: 124 - - """ - - # Class to use when creating address objects - _address_class = IPv6Address - - def __init__(self, address, strict=True): - """Instantiate a new IPv6 Network object. - - Args: - address: A string or integer representing the IPv6 network or the - IP and prefix/netmask. - '2001:db8::/128' - '2001:db8:0000:0000:0000:0000:0000:0000/128' - '2001:db8::' - are all functionally the same in IPv6. That is to say, - failing to provide a subnetmask will create an object with - a mask of /128. - - Additionally, an integer can be passed, so - IPv6Network('2001:db8::') == - IPv6Network(42540766411282592856903984951653826560) - or, more generally - IPv6Network(int(IPv6Network('2001:db8::'))) == - IPv6Network('2001:db8::') - - strict: A boolean. If true, ensure that we have been passed - A true network address, eg, 2001:db8::1000/124 and not an - IP address on a network, eg, 2001:db8::1/124. - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - NetmaskValueError: If the netmask isn't valid for - an IPv6 address. - ValueError: If strict was True and a network address was not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Efficient constructor from integer or packed address - if isinstance(address, (bytes, _compat_int_types)): - self.network_address = IPv6Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - self.network_address = IPv6Address(address[0]) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv6Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - - self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv6Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv6Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the - Subnet-Router anycast address. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast + 1): - yield self._address_class(x) - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return (self.network_address.is_site_local and - self.broadcast_address.is_site_local) - - -class _IPv6Constants(object): - - _linklocal_network = IPv6Network('fe80::/10') - - _multicast_network = IPv6Network('ff00::/8') - - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), - IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), - IPv6Network('2001:10::/28'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), - IPv6Network('800::/5'), IPv6Network('1000::/4'), - IPv6Network('4000::/3'), IPv6Network('6000::/3'), - IPv6Network('8000::/3'), IPv6Network('A000::/3'), - IPv6Network('C000::/3'), IPv6Network('E000::/4'), - IPv6Network('F000::/5'), IPv6Network('F800::/6'), - IPv6Network('FE00::/9'), - ] - - _sitelocal_network = IPv6Network('fec0::/10') - - -IPv6Address._constants = _IPv6Constants From 796d09b4103c6ecbf8f4b10f97fbdd84e8c46552 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:17:39 +0100 Subject: [PATCH 18/65] Remove six leafovers --- readthedocs/core/utils/extend.py | 2 +- readthedocs/rtd_tests/tests/test_views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py index c01c76eee..ca8e3f40b 100644 --- a/readthedocs/core/utils/extend.py +++ b/readthedocs/core/utils/extend.py @@ -39,7 +39,7 @@ class SettingsOverrideMeta(type): return getattr(proxy_class, attr) -class SettingsOverrideObject(six.with_metaclass(SettingsOverrideMeta, object)): +class SettingsOverrideObject(metaclass=SettingsOverrideMeta): """ Base class for creating class that can be overridden. diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py index dd64a438a..56b669979 100644 --- a/readthedocs/rtd_tests/tests/test_views.py +++ b/readthedocs/rtd_tests/tests/test_views.py @@ -3,8 +3,8 @@ import mock from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse -from django.utils.six.moves.urllib.parse import urlsplit from django_dynamic_fixture import get, new +from urllib.parse import urlsplit from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Build From c2a662cad6dfd3e57078cbdf59e025eb01250a82 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:20:11 +0100 Subject: [PATCH 19/65] Bug fix on types checking --- readthedocs/config/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index a69b8ed77..c366b0507 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -43,7 +43,7 @@ class ValidationError(Exception): def validate_list(value): """Check if ``value`` is an iterable.""" - if isinstance(value, (dict, (str,))): + if isinstance(value, (dict, str)): raise ValidationError(value, INVALID_LIST) if not hasattr(value, '__iter__'): raise ValidationError(value, INVALID_LIST) From 686c8201d7bac90afadecb4860e17971be9ba3fa Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:28:59 +0100 Subject: [PATCH 20/65] Import missing modules removed by mistake --- readthedocs/config/tests/test_find.py | 2 ++ readthedocs/rtd_tests/tests/test_post_commit_hooks.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index 048a6c9b9..4ebcb783c 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -1,3 +1,5 @@ +import os + from readthedocs.config.find import find_one from .utils import apply_fs diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index 6b4a6db07..f9b310a62 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -4,7 +4,7 @@ import logging import mock from django.test import TestCase from django_dynamic_fixture import get -from urllib.parse import urlparse +from urllib.parse import urlparse, urlencode from readthedocs.builds.models import Version from readthedocs.projects.models import Feature, Project From 25eb8c480cdd24e340ba538acf252f1292cd6cd4 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:29:11 +0100 Subject: [PATCH 21/65] Keep __future__ in conf.py because we build with Py2 also --- readthedocs/rtd_tests/files/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 7bdfdbe06..331f30602 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import division, print_function, unicode_literals from datetime import datetime from recommonmark.parser import CommonMarkParser From 06b07cc6ffd3aa64ec84442e0821f4a9425b5ac4 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:31:00 +0100 Subject: [PATCH 22/65] Make our generated conf.py compatible with Py2 and Py3 --- readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl | 7 ++++--- readthedocs/rtd_tests/files/conf.py | 3 ++- readthedocs/templates/sphinx/conf.py.conf | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl index e36cdd16a..0ec2b9c33 100644 --- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl @@ -13,6 +13,7 @@ # https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl # +from __future__ import absolute_import, division, print_function, unicode_literals import importlib import sys @@ -81,9 +82,9 @@ context = { ("{{ slug }}", "{{ url }}"),{% endfor %} ], 'slug': '{{ project.slug }}', - 'name': u'{{ project.name }}', - 'rtd_language': u'{{ project.language }}', - 'programming_language': u'{{ project.programming_language }}', + 'name': '{{ project.name }}', + 'rtd_language': '{{ project.language }}', + 'programming_language': '{{ project.programming_language }}', 'canonical_url': '{{ project.get_canonical_url }}', 'analytics_code': '{{ project.analytics_code }}', 'single_version': {{ project.single_version }}, diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 331f30602..11f872849 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals + from datetime import datetime from recommonmark.parser import CommonMarkParser @@ -12,7 +13,7 @@ source_parsers = { '.md': CommonMarkParser, } master_doc = 'index' -project = u'Pip' +project = 'Pip' copyright = str(datetime.now().year) version = '0.8.1' release = '0.8.1' diff --git a/readthedocs/templates/sphinx/conf.py.conf b/readthedocs/templates/sphinx/conf.py.conf index 43e7b58b2..520670cca 100644 --- a/readthedocs/templates/sphinx/conf.py.conf +++ b/readthedocs/templates/sphinx/conf.py.conf @@ -13,7 +13,7 @@ source_parsers = { '.md': CommonMarkParser, } master_doc = 'index' -project = u'{{ project.name }}' +project = '{{ project.name }}' copyright = str(datetime.now().year) version = '{{ version.verbose_name }}' release = '{{ version.verbose_name }}' @@ -23,6 +23,6 @@ htmlhelp_basename = '{{ project.slug }}' html_theme = 'sphinx_rtd_theme' file_insertion_enabled = False latex_documents = [ - ('index', '{{ project.slug }}.tex', u'{{ project.name }} Documentation', - u'{{ project.copyright }}', 'manual'), + ('index', '{{ project.slug }}.tex', '{{ project.name }} Documentation', + '{{ project.copyright }}', 'manual'), ] From e066c0d983775bb9be87fa0c10c68e27c2265ef1 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:43:13 +0100 Subject: [PATCH 23/65] Import missing modules --- readthedocs/core/templatetags/core_tags.py | 2 +- readthedocs/vcs_support/backends/bzr.py | 1 + readthedocs/vcs_support/backends/svn.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index e1b9e6f79..304ca3aff 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,7 +1,7 @@ """Template tags for core app.""" import hashlib -from urllib.parse import urlparse +from urllib.parse import urlparse, urlencode from django import template from django.conf import settings diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py index 9c9787040..ebfb5574f 100644 --- a/readthedocs/vcs_support/backends/bzr.py +++ b/readthedocs/vcs_support/backends/bzr.py @@ -3,6 +3,7 @@ import csv import re +from io import StringIO from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py index b4ad61303..d591dc880 100644 --- a/readthedocs/vcs_support/backends/svn.py +++ b/readthedocs/vcs_support/backends/svn.py @@ -2,6 +2,7 @@ """Subversion-related utilities.""" import csv +from io import StringIO from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion From d606c25d10b7b8979e4b0bc3da6d5963ed93968e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:45:29 +0100 Subject: [PATCH 24/65] Upgrade common submodule --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index a6cb6bbaf..890625b9b 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit a6cb6bbafb3cf93bfad7bab98c6636021719cc48 +Subproject commit 890625b9bfca1981a3f68a9fb7d168bba714421d From 3219f7acbdde68e889c828920d0b181bae93277e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 21:51:36 +0100 Subject: [PATCH 25/65] Run pre-commit on all the files $ pre-commit run --files `find readthedocs -name '*'` --- readthedocs/__init__.py | 1 + readthedocs/analytics/__init__.py | 5 +- readthedocs/analytics/apps.py | 3 +- readthedocs/analytics/tasks.py | 36 +- readthedocs/analytics/utils.py | 21 +- readthedocs/api/base.py | 60 +- readthedocs/api/utils.py | 19 +- readthedocs/builds/admin.py | 14 +- readthedocs/builds/constants.py | 1 + readthedocs/builds/forms.py | 5 +- readthedocs/builds/managers.py | 5 +- readthedocs/builds/models.py | 145 +- readthedocs/builds/querysets.py | 14 +- readthedocs/builds/signals.py | 3 +- readthedocs/builds/syncers.py | 53 +- readthedocs/builds/urls.py | 1 + readthedocs/builds/utils.py | 1 + readthedocs/builds/version_slug.py | 14 +- readthedocs/builds/views.py | 19 +- readthedocs/config/__init__.py | 1 + readthedocs/config/config.py | 82 +- readthedocs/config/find.py | 1 + readthedocs/config/models.py | 1 + readthedocs/config/parser.py | 3 +- readthedocs/config/validation.py | 13 +- readthedocs/constants.py | 3 +- readthedocs/core/__init__.py | 1 + readthedocs/core/adapters.py | 18 +- readthedocs/core/admin.py | 11 +- readthedocs/core/apps.py | 1 + readthedocs/core/backends.py | 8 +- readthedocs/core/context_processors.py | 9 +- readthedocs/core/fields.py | 5 +- readthedocs/core/fixtures/flag_types.json | 28 +- readthedocs/core/forms.py | 2 + .../core/management/commands/archive.py | 10 +- .../core/management/commands/clean_builds.py | 20 +- .../core/management/commands/import_github.py | 8 +- .../commands/import_github_language.py | 17 +- .../commands/provision_elasticsearch.py | 11 +- readthedocs/core/management/commands/pull.py | 3 +- .../commands/reindex_elasticsearch.py | 22 +- .../core/management/commands/set_metadata.py | 9 +- .../core/management/commands/symlink.py | 6 +- .../core/management/commands/update_api.py | 3 +- .../management/commands/update_versions.py | 5 +- readthedocs/core/middleware.py | 83 +- readthedocs/core/mixins.py | 5 +- readthedocs/core/models.py | 13 +- readthedocs/core/permissions.py | 3 +- readthedocs/core/resolver.py | 67 +- readthedocs/core/signals.py | 10 +- .../static/core/font/fontawesome-webfont.svg | 1418 ++++++++--------- readthedocs/core/symlink.py | 110 +- readthedocs/core/tasks.py | 24 +- readthedocs/core/templatetags/core_tags.py | 31 +- readthedocs/core/templatetags/privacy_tags.py | 5 +- readthedocs/core/urls/__init__.py | 81 +- readthedocs/core/urls/single_version.py | 35 +- readthedocs/core/urls/subdomain.py | 35 +- readthedocs/core/utils/__init__.py | 1 - readthedocs/core/utils/extend.py | 6 +- readthedocs/core/utils/tasks/__init__.py | 3 +- .../core/utils/tasks/permission_checks.py | 3 +- readthedocs/core/utils/tasks/public.py | 18 +- readthedocs/core/utils/tasks/retrieve.py | 4 +- readthedocs/core/views/__init__.py | 22 +- readthedocs/core/views/hooks.py | 78 +- readthedocs/core/views/serve.py | 32 +- readthedocs/doc_builder/backends/mkdocs.py | 46 +- readthedocs/doc_builder/backends/sphinx.py | 86 +- readthedocs/doc_builder/base.py | 15 +- readthedocs/doc_builder/config.py | 5 +- readthedocs/doc_builder/constants.py | 5 +- readthedocs/doc_builder/environments.py | 136 +- readthedocs/doc_builder/exceptions.py | 5 +- readthedocs/doc_builder/loader.py | 17 +- .../doc_builder/python_environments.py | 61 +- readthedocs/doc_builder/signals.py | 5 +- .../templates/doc_builder/data.js.tmpl | 2 +- readthedocs/gold/__init__.py | 1 + readthedocs/gold/admin.py | 1 + readthedocs/gold/apps.py | 1 + readthedocs/gold/forms.py | 20 +- readthedocs/gold/signals.py | 5 +- readthedocs/gold/templates/gold/projects.html | 1 - .../templates/gold/subscription_form.html | 2 +- readthedocs/gold/urls.py | 25 +- readthedocs/gold/views.py | 21 +- readthedocs/integrations/admin.py | 37 +- readthedocs/integrations/models.py | 3 +- readthedocs/integrations/utils.py | 1 + readthedocs/notifications/__init__.py | 4 +- readthedocs/notifications/apps.py | 1 + readthedocs/notifications/backends.py | 1 + readthedocs/notifications/constants.py | 4 +- readthedocs/notifications/forms.py | 5 +- readthedocs/notifications/notification.py | 20 +- readthedocs/notifications/storages.py | 36 +- readthedocs/notifications/urls.py | 17 +- readthedocs/notifications/views.py | 37 +- readthedocs/oauth/__init__.py | 1 + readthedocs/oauth/admin.py | 1 + readthedocs/oauth/apps.py | 3 +- readthedocs/oauth/models.py | 45 +- readthedocs/oauth/notifications.py | 11 +- readthedocs/oauth/querysets.py | 5 +- readthedocs/oauth/services/__init__.py | 7 +- readthedocs/oauth/services/base.py | 4 +- readthedocs/oauth/services/bitbucket.py | 76 +- readthedocs/oauth/services/github.py | 64 +- readthedocs/oauth/services/gitlab.py | 18 +- readthedocs/oauth/tasks.py | 1 + readthedocs/oauth/utils.py | 5 +- readthedocs/payments/forms.py | 89 +- readthedocs/payments/mixins.py | 1 + readthedocs/payments/utils.py | 1 + readthedocs/profiles/urls/private.py | 6 +- readthedocs/profiles/urls/public.py | 7 +- readthedocs/profiles/views.py | 24 +- readthedocs/projects/__init__.py | 1 + readthedocs/projects/admin.py | 49 +- readthedocs/projects/apps.py | 3 +- readthedocs/projects/backends/views.py | 3 +- readthedocs/projects/constants.py | 10 +- readthedocs/projects/exceptions.py | 17 +- readthedocs/projects/feeds.py | 19 +- readthedocs/projects/fixtures/test_auth.json | 2 +- readthedocs/projects/forms.py | 141 +- .../commands/import_project_from_live.py | 16 +- readthedocs/projects/models.py | 578 +++++-- readthedocs/projects/notifications.py | 3 +- readthedocs/projects/querysets.py | 14 +- readthedocs/projects/signals.py | 15 +- readthedocs/projects/tasks.py | 232 +-- .../projects/templatetags/projects_tags.py | 10 +- readthedocs/projects/urls/private.py | 271 +++- readthedocs/projects/urls/public.py | 94 +- readthedocs/projects/utils.py | 1 + readthedocs/projects/validators.py | 3 +- readthedocs/projects/version_handling.py | 4 +- readthedocs/projects/views/base.py | 4 +- readthedocs/projects/views/mixins.py | 3 +- readthedocs/projects/views/public.py | 99 +- readthedocs/redirects/admin.py | 1 + readthedocs/redirects/managers.py | 7 +- readthedocs/redirects/models.py | 95 +- readthedocs/redirects/utils.py | 12 +- readthedocs/restapi/client.py | 2 +- readthedocs/restapi/permissions.py | 6 +- readthedocs/restapi/serializers.py | 25 +- readthedocs/restapi/signals.py | 3 +- readthedocs/restapi/urls.py | 20 +- readthedocs/restapi/utils.py | 53 +- readthedocs/restapi/views/core_views.py | 15 +- readthedocs/restapi/views/footer_views.py | 18 +- readthedocs/restapi/views/integrations.py | 40 +- readthedocs/restapi/views/model_views.py | 61 +- readthedocs/restapi/views/search_views.py | 59 +- readthedocs/restapi/views/task_views.py | 22 +- readthedocs/search/indexes.py | 91 +- readthedocs/search/lib.py | 268 ++-- readthedocs/search/parse_json.py | 25 +- readthedocs/search/signals.py | 7 +- readthedocs/search/utils.py | 45 +- readthedocs/search/views.py | 17 +- .../email/email_confirmation_message.html | 1 - readthedocs/templates/core/badge_markup.html | 2 +- .../templates/core/build_list_detailed.html | 1 - readthedocs/templates/core/project_list.html | 1 - .../templates/core/project_list_detailed.html | 2 +- readthedocs/templates/core/widesearchbar.html | 2 +- readthedocs/templates/dnt-policy.txt | 50 +- .../templates/flagging/flag_confirm.html | 2 +- .../templates/flagging/flag_success.html | 2 +- .../projects/domain_confirm_delete.html | 2 - .../templates/projects/domain_form.html | 1 - .../templates/projects/domain_list.html | 3 +- .../templates/projects/project_analytics.html | 4 +- .../projects/project_version_list.html | 2 +- .../templates/search/elastic_search.html | 8 +- readthedocs/templates/style_catalog.html | 12 +- readthedocs/urls.py | 40 +- readthedocs/vcs_support/backends/__init__.py | 1 + readthedocs/vcs_support/backends/bzr.py | 1 + readthedocs/vcs_support/backends/git.py | 13 +- readthedocs/vcs_support/backends/hg.py | 7 +- readthedocs/vcs_support/backends/svn.py | 30 +- readthedocs/vcs_support/base.py | 5 +- readthedocs/vcs_support/utils.py | 54 +- readthedocs/worker.py | 3 +- readthedocs/wsgi.py | 5 +- 192 files changed, 4005 insertions(+), 2536 deletions(-) diff --git a/readthedocs/__init__.py b/readthedocs/__init__.py index 1c8afb1a0..8f8c9ee7c 100644 --- a/readthedocs/__init__.py +++ b/readthedocs/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Read the Docs.""" import os.path diff --git a/readthedocs/analytics/__init__.py b/readthedocs/analytics/__init__.py index f2531d76e..b8f7ef290 100644 --- a/readthedocs/analytics/__init__.py +++ b/readthedocs/analytics/__init__.py @@ -1,3 +1,4 @@ -"""App init""" +# -*- coding: utf-8 -*- +"""App init.""" -default_app_config = 'readthedocs.analytics.apps.AnalyticsAppConfig' # noqa +default_app_config = 'readthedocs.analytics.apps.AnalyticsAppConfig' # noqa diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py index 219a787dc..3bfa8a355 100644 --- a/readthedocs/analytics/apps.py +++ b/readthedocs/analytics/apps.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django app config for the analytics app.""" from django.apps import AppConfig @@ -5,7 +6,7 @@ from django.apps import AppConfig class AnalyticsAppConfig(AppConfig): - """Analytics app init code""" + """Analytics app init code.""" name = 'readthedocs.analytics' verbose_name = 'Analytics' diff --git a/readthedocs/analytics/tasks.py b/readthedocs/analytics/tasks.py index b79102fc6..02b0a7524 100644 --- a/readthedocs/analytics/tasks.py +++ b/readthedocs/analytics/tasks.py @@ -1,4 +1,5 @@ -"""Tasks for Read the Docs' analytics""" +# -*- coding: utf-8 -*- +"""Tasks for Read the Docs' analytics.""" from django.conf import settings @@ -9,24 +10,24 @@ from .utils import send_to_analytics DEFAULT_PARAMETERS = { - 'v': '1', # analytics version (always 1) - 'aip': '1', # anonymize IP + 'v': '1', # analytics version (always 1) + 'aip': '1', # anonymize IP 'tid': settings.GLOBAL_ANALYTICS_CODE, # User data - 'uip': None, # User IP address - 'ua': None, # User agent + 'uip': None, # User IP address + 'ua': None, # User agent # Application info 'an': 'Read the Docs', - 'av': readthedocs.__version__, # App version + 'av': readthedocs.__version__, # App version } @app.task(queue='web') def analytics_pageview(url, title=None, **kwargs): """ - Send a pageview to Google Analytics + Send a pageview to Google Analytics. :see: https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters :param url: the URL of the pageview @@ -35,8 +36,8 @@ def analytics_pageview(url, title=None, **kwargs): """ data = { 't': 'pageview', - 'dl': url, # URL of the pageview (required) - 'dt': title, # Title of the page + 'dl': url, # URL of the pageview (required) + 'dt': title, # Title of the page } data.update(DEFAULT_PARAMETERS) data.update(kwargs) @@ -44,9 +45,12 @@ def analytics_pageview(url, title=None, **kwargs): @app.task(queue='web') -def analytics_event(event_category, event_action, event_label=None, event_value=None, **kwargs): +def analytics_event( + event_category, event_action, event_label=None, event_value=None, + **kwargs +): """ - Send an analytics event to Google Analytics + Send an analytics event to Google Analytics. :see: https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#event :param event_category: the category of the event @@ -56,11 +60,11 @@ def analytics_event(event_category, event_action, event_label=None, event_value= :param kwargs: extra event parameters to send to GA """ data = { - 't': 'event', # GA event - don't change - 'ec': event_category, # Event category (required) - 'ea': event_action, # Event action (required) - 'el': event_label, # Event label - 'ev': event_value, # Event value (numeric) + 't': 'event', # GA event - don't change + 'ec': event_category, # Event category (required) + 'ea': event_action, # Event action (required) + 'el': event_label, # Event label + 'ev': event_value, # Event value (numeric) } data.update(DEFAULT_PARAMETERS) data.update(kwargs) diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py index 5c74cfd1e..8c06e6001 100644 --- a/readthedocs/analytics/utils.py +++ b/readthedocs/analytics/utils.py @@ -1,4 +1,5 @@ -"""Utilities related to analytics""" +# -*- coding: utf-8 -*- +"""Utilities related to analytics.""" import hashlib import ipaddress @@ -11,11 +12,11 @@ from django.utils.encoding import force_bytes, force_text from user_agents import parse -log = logging.getLogger(__name__) # noqa +log = logging.getLogger(__name__) # noqa def get_client_ip(request): - """Gets the real IP based on a request object""" + """Gets the real IP based on a request object.""" ip_address = request.META.get('REMOTE_ADDR') # Get the original IP address (eg. "X-Forwarded-For: client, proxy1, proxy2") @@ -27,7 +28,7 @@ def get_client_ip(request): def anonymize_ip_address(ip_address): - """Anonymizes an IP address by zeroing the last 2 bytes""" + """Anonymizes an IP address by zeroing the last 2 bytes.""" # Used to anonymize an IP by zero-ing out the last 2 bytes ip_mask = int('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000', 16) @@ -41,7 +42,7 @@ def anonymize_ip_address(ip_address): def anonymize_user_agent(user_agent): - """Anonymizes rare user agents""" + """Anonymizes rare user agents.""" # If the browser family is not recognized, this is a rare user agent parsed_ua = parse(user_agent) if parsed_ua.browser.family == 'Other' or parsed_ua.os.family == 'Other': @@ -51,7 +52,7 @@ def anonymize_user_agent(user_agent): def send_to_analytics(data): - """Sends data to Google Analytics""" + """Sends data to Google Analytics.""" if data.get('uip') and data.get('ua'): data['cid'] = generate_client_id(data['uip'], data['ua']) @@ -69,7 +70,7 @@ def send_to_analytics(data): resp = requests.post( 'https://www.google-analytics.com/collect', data=data, - timeout=3, # seconds + timeout=3, # seconds ) except requests.Timeout: log.warning('Timeout sending to Google Analytics') @@ -80,10 +81,10 @@ def send_to_analytics(data): def generate_client_id(ip_address, user_agent): """ - Create an advertising ID + Create an advertising ID. - This simplifies things but essentially if a user has the same IP and same UA, - this will treat them as the same user for analytics purposes + This simplifies things but essentially if a user has the same IP and same + UA, this will treat them as the same user for analytics purposes """ salt = b'advertising-client-id' diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py index 75e984d42..a70fb0245 100644 --- a/readthedocs/api/base.py +++ b/readthedocs/api/base.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """API resources.""" import logging @@ -69,7 +70,9 @@ class ProjectResource(ModelResource): # Force this in an ugly way, at least should do "reverse" deserialized['users'] = ['/api/v1/user/%s/' % request.user.id] bundle = self.build_bundle( - data=dict_strip_unicode_keys(deserialized), request=request) + data=dict_strip_unicode_keys(deserialized), + request=request, + ) self.is_valid(bundle) updated_bundle = self.obj_create(bundle, request=request) return HttpCreated(location=self.get_resource_uri(updated_bundle)) @@ -78,14 +81,20 @@ class ProjectResource(ModelResource): return [ url( r'^(?P%s)/schema/$' % self._meta.resource_name, - self.wrap_view('get_schema'), name='api_get_schema'), + self.wrap_view('get_schema'), + name='api_get_schema', + ), url( r'^(?P%s)/search%s$' % (self._meta.resource_name, trailing_slash()), - self.wrap_view('get_search'), name='api_get_search'), - url((r'^(?P%s)/(?P[a-z-_]+)/$') % - self._meta.resource_name, self.wrap_view('dispatch_detail'), - name='api_dispatch_detail'), + self.wrap_view('get_search'), + name='api_get_search', + ), + url( + (r'^(?P%s)/(?P[a-z-_]+)/$') % self._meta.resource_name, + self.wrap_view('dispatch_detail'), + name='api_dispatch_detail', + ), ] @@ -122,17 +131,23 @@ class VersionResource(ModelResource): return [ url( r'^(?P%s)/schema/$' % self._meta.resource_name, - self.wrap_view('get_schema'), name='api_get_schema'), + self.wrap_view('get_schema'), + name='api_get_schema', + ), url( r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/$' # noqa % self._meta.resource_name, self.wrap_view('dispatch_list'), - name='api_version_list'), - url(( - r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/(?P' - r'[a-z0-9-_.]+)/build/$') % - self._meta.resource_name, self.wrap_view('build_version'), - name='api_version_build_slug'), + name='api_version_list', + ), + url( + ( + r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/(?P' + r'[a-z0-9-_.]+)/build/$' + ) % self._meta.resource_name, + self.wrap_view('build_version'), + name='api_version_build_slug', + ), ] @@ -154,11 +169,15 @@ class FileResource(ModelResource): return [ url( r'^(?P%s)/schema/$' % self._meta.resource_name, - self.wrap_view('get_schema'), name='api_get_schema'), + self.wrap_view('get_schema'), + name='api_get_schema', + ), url( r'^(?P%s)/anchor%s$' % (self._meta.resource_name, trailing_slash()), - self.wrap_view('get_anchor'), name='api_get_anchor'), + self.wrap_view('get_anchor'), + name='api_get_anchor', + ), ] def get_anchor(self, request, **__): @@ -199,9 +218,12 @@ class UserResource(ModelResource): return [ url( r'^(?P%s)/schema/$' % self._meta.resource_name, - self.wrap_view('get_schema'), name='api_get_schema'), + self.wrap_view('get_schema'), + name='api_get_schema', + ), url( - r'^(?P%s)/(?P[a-z-_]+)/$' % - self._meta.resource_name, self.wrap_view('dispatch_detail'), - name='api_dispatch_detail'), + r'^(?P%s)/(?P[a-z-_]+)/$' % self._meta.resource_name, + self.wrap_view('dispatch_detail'), + name='api_dispatch_detail', + ), ] diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py index e2b5e9c33..1f112a608 100644 --- a/readthedocs/api/utils.py +++ b/readthedocs/api/utils.py @@ -1,4 +1,5 @@ -"""Utility classes for api module""" +# -*- coding: utf-8 -*- +"""Utility classes for api module.""" import logging from django.utils.translation import ugettext @@ -16,14 +17,14 @@ class PostAuthentication(BasicAuthentication): """Require HTTP Basic authentication for any method other than GET.""" def is_authenticated(self, request, **kwargs): - val = super().is_authenticated(request, - **kwargs) - if request.method == "GET": + val = super().is_authenticated(request, **kwargs) + if request.method == 'GET': return True return val class EnhancedModelResource(ModelResource): + def obj_get_list(self, request=None, *_, **kwargs): # noqa """ A ORM-specific implementation of ``obj_get_list``. @@ -42,12 +43,16 @@ class EnhancedModelResource(ModelResource): try: return self.get_object_list(request).filter(**applicable_filters) except ValueError as e: - raise NotFound(ugettext("Invalid resource lookup data provided " - "(mismatched type).: %(error)s") - % {'error': e}) + raise NotFound( + ugettext( + 'Invalid resource lookup data provided ' + '(mismatched type).: %(error)s', + ) % {'error': e} + ) class OwnerAuthorization(Authorization): + def apply_limits(self, request, object_list): if request and hasattr(request, 'user') and request.method != 'GET': if request.user.is_authenticated: diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py index 59fe235ae..36b64339d 100644 --- a/readthedocs/builds/admin.py +++ b/readthedocs/builds/admin.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django admin interface for `~builds.models.Build` and related models.""" from django.contrib import admin @@ -12,8 +13,13 @@ class BuildCommandResultInline(admin.TabularInline): class BuildAdmin(admin.ModelAdmin): - fields = ('project', 'version', 'type', 'state', 'error', 'success', 'length', 'cold_storage') - list_display = ('id', 'project', 'version_name', 'success', 'type', 'state', 'date') + fields = ( + 'project', 'version', 'type', 'state', 'error', 'success', 'length', + 'cold_storage' + ) + list_display = ( + 'id', 'project', 'version_name', 'success', 'type', 'state', 'date' + ) list_filter = ('type', 'state', 'success') list_select_related = ('project', 'version') raw_id_fields = ('project', 'version') @@ -26,7 +32,9 @@ class BuildAdmin(admin.ModelAdmin): class VersionAdmin(GuardedModelAdmin): search_fields = ('slug', 'project__name') - list_display = ('slug', 'type', 'project', 'privacy_level', 'active', 'built') + list_display = ( + 'slug', 'type', 'project', 'privacy_level', 'active', 'built' + ) list_filter = ('type', 'privacy_level', 'active', 'built') raw_id_fields = ('project',) diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py index 660fb0b0f..1175874cf 100644 --- a/readthedocs/builds/constants.py +++ b/readthedocs/builds/constants.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Constants for the builds app.""" from django.conf import settings diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index ae655bfc9..13f72a5b9 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django forms for the builds app.""" from django import forms @@ -18,10 +19,10 @@ class VersionForm(forms.ModelForm): if self._is_default_version() and not active: msg = _( '{version} is the default version of the project, ' - 'it should be active.' + 'it should be active.', ) raise forms.ValidationError( - msg.format(version=self.instance.verbose_name) + msg.format(version=self.instance.verbose_name), ) return active diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py index bd8866058..d05308cc4 100644 --- a/readthedocs/builds/managers.py +++ b/readthedocs/builds/managers.py @@ -1,4 +1,5 @@ -"""Build and Version class model Managers""" +# -*- coding: utf-8 -*- +"""Build and Version class model Managers.""" from django.db import models @@ -37,7 +38,7 @@ class VersionManagerBase(models.Manager): # no direct members. queryset_class = get_override_class( VersionQuerySet, - VersionQuerySet._default_class # pylint: disable=protected-access + VersionQuerySet._default_class, # pylint: disable=protected-access ) return super().from_queryset(queryset_class, class_name) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index c11fa13d0..b6a5b0881 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Models for the builds app.""" import logging @@ -49,7 +50,10 @@ from .version_slug import VersionSlugField DEFAULT_VERSION_PRIVACY_LEVEL = getattr( - settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public') + settings, + 'DEFAULT_VERSION_PRIVACY_LEVEL', + 'public', +) log = logging.getLogger(__name__) @@ -89,7 +93,10 @@ class Version(models.Model): #: filesystem to determine how the paths for this version are called. It #: must not be used for any other identifying purposes. slug = VersionSlugField( - _('Slug'), max_length=255, populate_from='verbose_name') + _('Slug'), + max_length=255, + populate_from='verbose_name', + ) supported = models.BooleanField(_('Supported'), default=True) active = models.BooleanField(_('Active'), default=False) @@ -113,7 +120,8 @@ class Version(models.Model): permissions = ( # Translators: Permission around whether a user can view the # version - ('view_version', _('View Version')),) + ('view_version', _('View Version')), + ) def __str__(self): return ugettext( @@ -121,7 +129,8 @@ class Version(models.Model): version=self.verbose_name, project=self.project, pk=self.pk, - )) + ), + ) @property def config(self): @@ -132,9 +141,8 @@ class Version(models.Model): :rtype: dict """ last_build = ( - self.builds.filter(state='finished', success=True) - .order_by('-date') - .first() + self.builds.filter(state='finished', + success=True).order_by('-date').first() ) return last_build.config @@ -177,7 +185,9 @@ class Version(models.Model): # If we came that far it's not a special version nor a branch or tag. # Therefore just return the identifier to make a safe guess. - log.debug('TODO: Raise an exception here. Testing what cases it happens') + log.debug( + 'TODO: Raise an exception here. Testing what cases it happens' + ) return self.identifier def get_absolute_url(self): @@ -191,7 +201,9 @@ class Version(models.Model): ) private = self.privacy_level == PRIVATE return self.project.get_docs_url( - version_slug=self.slug, private=private) + version_slug=self.slug, + private=private, + ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ """Add permissions to the Version for all owners on save.""" @@ -200,15 +212,24 @@ class Version(models.Model): for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( - type='app', task=tasks.symlink_project, args=[self.project.pk]) + type='app', + task=tasks.symlink_project, + args=[self.project.pk], + ) return obj def delete(self, *args, **kwargs): # pylint: disable=arguments-differ from readthedocs.projects import tasks log.info('Removing files for version %s', self.slug) - broadcast(type='app', task=tasks.clear_artifacts, args=[self.get_artifact_paths()]) broadcast( - type='app', task=tasks.symlink_project, args=[self.project.pk]) + type='app', task=tasks.clear_artifacts, + args=[self.get_artifact_paths()] + ) + broadcast( + type='app', + task=tasks.symlink_project, + args=[self.project.pk], + ) super().delete(*args, **kwargs) @property @@ -238,19 +259,27 @@ class Version(models.Model): data['PDF'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['HTML'] = project.get_production_media_url( - 'htmlzip', self.slug) + 'htmlzip', + self.slug, + ) if project.has_epub(self.slug): data['Epub'] = project.get_production_media_url( - 'epub', self.slug) + 'epub', + self.slug, + ) else: if project.has_pdf(self.slug): data['pdf'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['htmlzip'] = project.get_production_media_url( - 'htmlzip', self.slug) + 'htmlzip', + self.slug, + ) if project.has_epub(self.slug): data['epub'] = project.get_production_media_url( - 'epub', self.slug) + 'epub', + self.slug, + ) return data def get_conf_py_path(self): @@ -278,7 +307,8 @@ class Version(models.Model): paths.append( self.project.get_production_media_path( type_=type_, - version_slug=self.slug), + version_slug=self.slug, + ), ) paths.append(self.project.rtd_build_path(version=self.slug)) @@ -300,7 +330,12 @@ class Version(models.Model): log.exception('Build path cleanup failed') def get_github_url( - self, docroot, filename, source_suffix='.rst', action='view'): + self, + docroot, + filename, + source_suffix='.rst', + action='view', + ): """ Return a GitHub URL for a given filename. @@ -342,7 +377,12 @@ class Version(models.Model): ) def get_gitlab_url( - self, docroot, filename, source_suffix='.rst', action='view'): + self, + docroot, + filename, + source_suffix='.rst', + action='view', + ): repo_url = self.project.repo if 'gitlab' not in repo_url: return '' @@ -439,13 +479,28 @@ class Build(models.Model): """Build data.""" project = models.ForeignKey( - Project, verbose_name=_('Project'), related_name='builds') + Project, + verbose_name=_('Project'), + related_name='builds', + ) version = models.ForeignKey( - Version, verbose_name=_('Version'), null=True, related_name='builds') + Version, + verbose_name=_('Version'), + null=True, + related_name='builds', + ) type = models.CharField( - _('Type'), max_length=55, choices=BUILD_TYPES, default='html') + _('Type'), + max_length=55, + choices=BUILD_TYPES, + default='html', + ) state = models.CharField( - _('State'), max_length=55, choices=BUILD_STATE, default='finished') + _('State'), + max_length=55, + choices=BUILD_STATE, + default='finished', + ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) @@ -455,16 +510,26 @@ class Build(models.Model): error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( - _('Commit'), max_length=255, null=True, blank=True) + _('Commit'), + max_length=255, + null=True, + blank=True, + ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( - _('Builder'), max_length=255, null=True, blank=True) + _('Builder'), + max_length=255, + null=True, + blank=True, + ) cold_storage = models.NullBooleanField( - _('Cold Storage'), help_text='Build steps stored outside the database.') + _('Cold Storage'), + help_text='Build steps stored outside the database.', + ) # Manager @@ -491,14 +556,11 @@ class Build(models.Model): date = self.date or timezone.now() if self.project is not None and self.version is not None: return ( - Build.objects - .filter( + Build.objects.filter( project=self.project, version=self.version, date__lt=date, - ) - .order_by('-date') - .first() + ).order_by('-date').first() ) return None @@ -508,9 +570,9 @@ class Build(models.Model): Get the config used for this build. Since we are saving the config into the JSON field only when it differs - from the previous one, this helper returns the correct JSON used in - this Build object (it could be stored in this object or one of the - previous ones). + from the previous one, this helper returns the correct JSON used in this + Build object (it could be stored in this object or one of the previous + ones). """ if self.CONFIG_KEY in self._config: return Build.objects.get(pk=self._config[self.CONFIG_KEY])._config @@ -538,8 +600,8 @@ class Build(models.Model): """ if self.pk is None or self._config_changed: previous = self.previous - if (previous is not None and - self._config and self._config == previous.config): + if (previous is not None and self._config and + self._config == previous.config): previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} super().save(*args, **kwargs) @@ -553,7 +615,8 @@ class Build(models.Model): self.project.users.all().values_list('username', flat=True), ), pk=self.pk, - )) + ), + ) def get_absolute_url(self): return reverse('builds_detail', args=[self.project.slug, self.pk]) @@ -594,7 +657,10 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): """Build command for a ``Build``.""" build = models.ForeignKey( - Build, verbose_name=_('Build'), related_name='commands') + Build, + verbose_name=_('Build'), + related_name='commands', + ) command = models.TextField(_('Command')) description = models.TextField(_('Description'), blank=True) @@ -613,7 +679,8 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): def __str__(self): return ( ugettext('Build command {pk} for build {build}') - .format(pk=self.pk, build=self.build)) + .format(pk=self.pk, build=self.build) + ) @property def run_time(self): diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py index a2a349388..5c3778257 100644 --- a/readthedocs/builds/querysets.py +++ b/readthedocs/builds/querysets.py @@ -1,4 +1,5 @@ -"""Build and Version QuerySet classes""" +# -*- coding: utf-8 -*- +"""Build and Version QuerySet classes.""" from django.db import models from guardian.shortcuts import get_objects_for_user @@ -35,7 +36,9 @@ class VersionQuerySetBase(models.QuerySet): return queryset def protected(self, user=None, project=None, only_active=True): - queryset = self.filter(privacy_level__in=[constants.PUBLIC, constants.PROTECTED]) + queryset = self.filter( + privacy_level__in=[constants.PUBLIC, constants.PROTECTED] + ) if user: queryset = self._add_user_repos(queryset, user) if project: @@ -58,10 +61,10 @@ class VersionQuerySetBase(models.QuerySet): return self.public(user, only_active=False) def for_project(self, project): - """Return all versions for a project, including translations""" + """Return all versions for a project, including translations.""" return self.filter( models.Q(project=project) | - models.Q(project__main_language_project=project) + models.Q(project__main_language_project=project), ) @@ -117,8 +120,7 @@ class RelatedBuildQuerySetBase(models.QuerySet): if user.is_authenticated: user_queryset = get_objects_for_user(user, 'builds.view_version') pks = user_queryset.values_list('pk', flat=True) - queryset = self.filter( - build__version__pk__in=pks) | queryset + queryset = self.filter(build__version__pk__in=pks,) | queryset return queryset.distinct() def public(self, user=None, project=None): diff --git a/readthedocs/builds/signals.py b/readthedocs/builds/signals.py index c9bb49cc7..fb16ca75d 100644 --- a/readthedocs/builds/signals.py +++ b/readthedocs/builds/signals.py @@ -1,4 +1,5 @@ -"""Build signals""" +# -*- coding: utf-8 -*- +"""Build signals.""" import django.dispatch diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py index 5f02f966b..185e0d309 100644 --- a/readthedocs/builds/syncers.py +++ b/readthedocs/builds/syncers.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Classes to copy files between build and web servers. @@ -21,7 +22,7 @@ log = logging.getLogger(__name__) class BaseSyncer: - """A base object for syncers and pullers""" + """A base object for syncers and pullers.""" @classmethod def copy(cls, path, target, is_file=False, **kwargs): @@ -33,7 +34,7 @@ class LocalSyncer(BaseSyncer): @classmethod def copy(cls, path, target, is_file=False, **kwargs): """A copy command that works with files or directories.""" - log.info("Local Copy %s to %s", path, target) + log.info('Local Copy %s to %s', path, target) if is_file: if path == target: # Don't copy the same file over itself @@ -59,28 +60,31 @@ class RemoteSyncer(BaseSyncer): sync_user = getattr(settings, 'SYNC_USER', getpass.getuser()) app_servers = getattr(settings, 'MULTIPLE_APP_SERVERS', []) if app_servers: - log.info("Remote Copy %s to %s on %s", path, target, app_servers) + log.info('Remote Copy %s to %s on %s', path, target, app_servers) for server in app_servers: - mkdir_cmd = ("ssh {}@{} mkdir -p {}".format(sync_user, server, target)) + mkdir_cmd = ( + 'ssh {}@{} mkdir -p {}'.format(sync_user, server, target) + ) ret = os.system(mkdir_cmd) if ret != 0: - log.debug("Copy error to app servers: cmd=%s", mkdir_cmd) + log.debug('Copy error to app servers: cmd=%s', mkdir_cmd) if is_file: - slash = "" + slash = '' else: - slash = "/" + slash = '/' # Add a slash when copying directories sync_cmd = ( - "rsync -e 'ssh -T' -av --delete {path}{slash} {user}@{server}:{target}" - .format( + "rsync -e 'ssh -T' -av --delete {path}{slash} {user}@{server}:{target}".format( path=path, slash=slash, user=sync_user, server=server, - target=target)) + target=target, + ) + ) ret = os.system(sync_cmd) if ret != 0: - log.debug("Copy error to app servers: cmd=%s", sync_cmd) + log.debug('Copy error to app servers: cmd=%s', sync_cmd) class DoubleRemotePuller(BaseSyncer): @@ -95,29 +99,32 @@ class DoubleRemotePuller(BaseSyncer): sync_user = getattr(settings, 'SYNC_USER', getpass.getuser()) app_servers = getattr(settings, 'MULTIPLE_APP_SERVERS', []) if not is_file: - path += "/" - log.info("Remote Copy %s to %s", path, target) + path += '/' + log.info('Remote Copy %s to %s', path, target) for server in app_servers: if not is_file: - mkdir_cmd = "ssh {user}@{server} mkdir -p {target}".format( - user=sync_user, server=server, target=target + mkdir_cmd = 'ssh {user}@{server} mkdir -p {target}'.format( + user=sync_user, + server=server, + target=target, ) ret = os.system(mkdir_cmd) if ret != 0: - log.debug("MkDir error to app servers: cmd=%s", mkdir_cmd) + log.debug('MkDir error to app servers: cmd=%s', mkdir_cmd) # Add a slash when copying directories sync_cmd = ( "ssh {user}@{server} 'rsync -av " - "--delete --exclude projects {user}@{host}:{path} {target}'" - .format( + "--delete --exclude projects {user}@{host}:{path} {target}'".format( host=host, path=path, user=sync_user, server=server, - target=target)) + target=target, + ) + ) ret = os.system(sync_cmd) if ret != 0: - log.debug("Copy error to app servers: cmd=%s", sync_cmd) + log.debug('Copy error to app servers: cmd=%s', sync_cmd) class RemotePuller(BaseSyncer): @@ -131,8 +138,8 @@ class RemotePuller(BaseSyncer): """ sync_user = getattr(settings, 'SYNC_USER', getpass.getuser()) if not is_file: - path += "/" - log.info("Remote Pull %s to %s", path, target) + path += '/' + log.info('Remote Pull %s to %s', path, target) if not is_file and not os.path.exists(target): safe_makedirs(target) # Add a slash when copying directories @@ -145,7 +152,7 @@ class RemotePuller(BaseSyncer): ret = os.system(sync_cmd) if ret != 0: log.debug( - "Copy error to app servers. Command: [%s] Return: [%s]", + 'Copy error to app servers. Command: [%s] Return: [%s]', sync_cmd, ret, ) diff --git a/readthedocs/builds/urls.py b/readthedocs/builds/urls.py index 07f787d0b..9c4216620 100644 --- a/readthedocs/builds/urls.py +++ b/readthedocs/builds/urls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL configuration for builds app.""" from django.conf.urls import url diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py index d2d49270c..e0025dc77 100644 --- a/readthedocs/builds/utils.py +++ b/readthedocs/builds/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utilities for the builds app.""" from readthedocs.projects.constants import ( diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py index 18ad3d19f..d3e8bbe14 100644 --- a/readthedocs/builds/version_slug.py +++ b/readthedocs/builds/version_slug.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Contains logic for handling version slugs. @@ -34,12 +35,10 @@ def get_fields_with_model(cls): prescrived in the Django docs. https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api """ - return [ - (f, f.model if f.model != cls else None) - for f in cls._meta.get_fields() - if not f.is_relation or f.one_to_one or - (f.many_to_one and f.related_model) - ] + return [(f, f.model if f.model != cls else None) + for f in cls._meta.get_fields() + if not f.is_relation or f.one_to_one or + (f.many_to_one and f.related_model)] # Regex breakdown: @@ -165,7 +164,8 @@ class VersionSlugField(models.CharField): count += 1 assert self.test_pattern.match(slug), ( - 'Invalid generated slug: {slug}'.format(slug=slug)) + 'Invalid generated slug: {slug}'.format(slug=slug) + ) return slug def pre_save(self, model_instance, add): diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py index 37a46ae3e..69ed90dad 100644 --- a/readthedocs/builds/views.py +++ b/readthedocs/builds/views.py @@ -35,7 +35,8 @@ class BuildBase: slug=self.project_slug, ) queryset = Build.objects.public( - user=self.request.user, project=self.project + user=self.request.user, + project=self.project, ) return queryset @@ -57,7 +58,9 @@ class BuildTriggerMixin: slug=version_slug, ) - update_docs_task, build = trigger_build(project=project, version=version) + update_docs_task, build = trigger_build( + project=project, version=version + ) if (update_docs_task, build) == (None, None): # Build was skipped messages.add_message( @@ -79,13 +82,15 @@ class BuildList(BuildBase, BuildTriggerMixin, ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - active_builds = self.get_queryset().exclude(state='finished' - ).values('id') + active_builds = self.get_queryset().exclude( + state='finished', + ).values('id') context['project'] = self.project context['active_builds'] = active_builds context['versions'] = Version.objects.public( - user=self.request.user, project=self.project + user=self.request.user, + project=self.project, ) context['build_qs'] = self.get_queryset() @@ -106,11 +111,11 @@ class BuildDetail(BuildBase, DetailView): def builds_redirect_list(request, project_slug): # pylint: disable=unused-argument return HttpResponsePermanentRedirect( - reverse('builds_project_list', args=[project_slug]) + reverse('builds_project_list', args=[project_slug]), ) def builds_redirect_detail(request, project_slug, pk): # pylint: disable=unused-argument return HttpResponsePermanentRedirect( - reverse('builds_detail', args=[project_slug, pk]) + reverse('builds_detail', args=[project_slug, pk]), ) diff --git a/readthedocs/config/__init__.py b/readthedocs/config/__init__.py index 314f6c394..fd6326a50 100644 --- a/readthedocs/config/__init__.py +++ b/readthedocs/config/__init__.py @@ -1,2 +1,3 @@ +# -*- coding: utf-8 -*- from .config import * # noqa from .parser import * # noqa diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 57e01439e..a397544e9 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -96,7 +96,7 @@ class ConfigOptionNotSupportedError(ConfigError): ) super().__init__( template.format(self.configuration), - CONFIG_NOT_SUPPORTED + CONFIG_NOT_SUPPORTED, ) @@ -137,9 +137,15 @@ class BuildConfigBase: """ PUBLIC_ATTRIBUTES = [ - 'version', 'formats', 'python', - 'conda', 'build', 'doctype', - 'sphinx', 'mkdocs', 'submodules', + 'version', + 'formats', + 'python', + 'conda', + 'build', + 'doctype', + 'sphinx', + 'mkdocs', + 'submodules', ] version = None @@ -235,9 +241,7 @@ class BuildConfigBase: # Get the highest version of the major series version if user only # gave us a version of '2', or '3' ver = max( - v - for v in self.get_valid_python_versions() - if v < ver + 1 + v for v in self.get_valid_python_versions() if v < ver + 1 ) return ver @@ -324,11 +328,12 @@ class BuildConfigV1(BuildConfigBase): def validate_output_base(self): """Validates that ``output_base`` exists and set its absolute path.""" assert 'output_base' in self.env_config, ( - '"output_base" required in "env_config"') + '"output_base" required in "env_config"' + ) output_base = os.path.abspath( os.path.join( self.env_config.get('output_base', self.base_path), - ) + ), ) return output_base @@ -345,8 +350,10 @@ class BuildConfigV1(BuildConfigBase): 'name', self.NAME_INVALID_MESSAGE.format( name=name, - name_re=name_re), - code=NAME_INVALID) + name_re=name_re, + ), + code=NAME_INVALID, + ) return name @@ -395,18 +402,16 @@ class BuildConfigV1(BuildConfigBase): # Prepend proper image name to user's image name build['image'] = '{}:{}'.format( DOCKER_DEFAULT_IMAGE, - build['image'] + build['image'], ) # Update docker default settings from image name if build['image'] in DOCKER_IMAGE_SETTINGS: - self.env_config.update( - DOCKER_IMAGE_SETTINGS[build['image']] - ) + self.env_config.update(DOCKER_IMAGE_SETTINGS[build['image']],) # Update docker settings from user config if 'DOCKER_IMAGE_SETTINGS' in self.env_config and \ build['image'] in self.env_config['DOCKER_IMAGE_SETTINGS']: self.env_config.update( - self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']] + self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']], ) # Allow to override specific project @@ -434,20 +439,22 @@ class BuildConfigV1(BuildConfigBase): self.error( 'python', self.PYTHON_INVALID_MESSAGE, - code=PYTHON_INVALID) + code=PYTHON_INVALID, + ) # Validate use_system_site_packages. if 'use_system_site_packages' in raw_python: - with self.catch_validation_error( - 'python.use_system_site_packages'): + with self.catch_validation_error('python.use_system_site_packages',): python['use_system_site_packages'] = validate_bool( - raw_python['use_system_site_packages']) + raw_python['use_system_site_packages'], + ) # Validate pip_install. if 'pip_install' in raw_python: with self.catch_validation_error('python.pip_install'): python['install_with_pip'] = validate_bool( - raw_python['pip_install']) + raw_python['pip_install'], + ) # Validate extra_requirements. if 'extra_requirements' in raw_python: @@ -456,22 +463,23 @@ class BuildConfigV1(BuildConfigBase): self.error( 'python.extra_requirements', self.PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE, - code=PYTHON_INVALID) + code=PYTHON_INVALID, + ) if not python['install_with_pip']: python['extra_requirements'] = [] else: for extra_name in raw_extra_requirements: - with self.catch_validation_error( - 'python.extra_requirements'): + with self.catch_validation_error('python.extra_requirements',): python['extra_requirements'].append( - validate_string(extra_name) + validate_string(extra_name), ) # Validate setup_py_install. if 'setup_py_install' in raw_python: with self.catch_validation_error('python.setup_py_install'): python['install_with_setup'] = validate_bool( - raw_python['setup_py_install']) + raw_python['setup_py_install'], + ) if 'version' in raw_python: with self.catch_validation_error('python.version'): @@ -505,7 +513,8 @@ class BuildConfigV1(BuildConfigBase): if 'file' in raw_conda: with self.catch_validation_error('conda.file'): conda_environment = validate_file( - raw_conda['file'], self.base_path + raw_conda['file'], + self.base_path, ) conda['environment'] = conda_environment @@ -758,7 +767,8 @@ class BuildConfigV2(BuildConfigBase): with self.catch_validation_error('python.extra_requirements'): extra_requirements = self.pop_config( - 'python.extra_requirements', [] + 'python.extra_requirements', + [], ) extra_requirements = validate_list(extra_requirements) if extra_requirements and not python['install_with_pip']: @@ -876,7 +886,8 @@ class BuildConfigV2(BuildConfigBase): if not configuration: configuration = None configuration = self.pop_config( - 'sphinx.configuration', configuration + 'sphinx.configuration', + configuration, ) if configuration is not None: configuration = validate_file(configuration, self.base_path) @@ -892,9 +903,8 @@ class BuildConfigV2(BuildConfigBase): """ Validates that the doctype is the same as the admin panel. - This a temporal validation, as the configuration file - should support per version doctype, but we need to - adapt the rtd code for that. + This a temporal validation, as the configuration file should support per + version doctype, but we need to adapt the rtd code for that. """ dashboard_doctype = self.defaults.get('doctype', 'sphinx') if self.doctype != dashboard_doctype: @@ -904,7 +914,7 @@ class BuildConfigV2(BuildConfigBase): if dashboard_doctype == 'mkdocs' or not self.sphinx: error_msg += ' but there is no "{}" key specified.'.format( - 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx' + 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx', ) else: error_msg += ' but your "sphinx.builder" key does not match.' @@ -966,8 +976,8 @@ class BuildConfigV2(BuildConfigBase): """ Checks that we don't have extra keys (invalid ones). - This should be called after all the validations are done - and all keys are popped from `self.raw_config`. + This should be called after all the validations are done and all keys + are popped from `self.raw_config`. """ msg = ( 'Invalid configuration option: {}. ' @@ -1059,7 +1069,7 @@ def load(path, env_config): if not filename: raise ConfigError( 'No configuration file found', - code=CONFIG_REQUIRED + code=CONFIG_REQUIRED, ) with open(filename, 'r') as configuration_file: try: diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py index ed49c765c..6fc3cb6a7 100644 --- a/readthedocs/config/find.py +++ b/readthedocs/config/find.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Helper functions to search files.""" import os diff --git a/readthedocs/config/models.py b/readthedocs/config/models.py index ce0604f18..1153fe3f9 100644 --- a/readthedocs/config/models.py +++ b/readthedocs/config/models.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Models for the response of the configuration object.""" from collections import namedtuple diff --git a/readthedocs/config/parser.py b/readthedocs/config/parser.py index 85e31782b..376774d61 100644 --- a/readthedocs/config/parser.py +++ b/readthedocs/config/parser.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """YAML parser for the RTD configuration file.""" import yaml @@ -11,8 +12,6 @@ class ParseError(Exception): """Parser related errors.""" - pass - def parse(stream): """ diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index c366b0507..b1840b880 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Validations for the RTD configuration file.""" import os @@ -26,7 +27,7 @@ class ValidationError(Exception): INVALID_PATH: 'path {value} does not exist', INVALID_STRING: 'expected string', INVALID_LIST: 'expected list', - VALUE_NOT_FOUND: '{value} not found' + VALUE_NOT_FOUND: '{value} not found', } def __init__(self, value, code, format_kwargs=None): @@ -60,9 +61,13 @@ def validate_choice(value, choices): """Check that ``value`` is in ``choices``.""" choices = validate_list(choices) if value not in choices: - raise ValidationError(value, INVALID_CHOICE, { - 'choices': ', '.join(map(str, choices)) - }) + raise ValidationError( + value, + INVALID_CHOICE, + { + 'choices': ', '.join(map(str, choices)), + }, + ) return value diff --git a/readthedocs/constants.py b/readthedocs/constants.py index 35cca6274..485b93739 100644 --- a/readthedocs/constants.py +++ b/readthedocs/constants.py @@ -1,4 +1,5 @@ -"""Common constants""" +# -*- coding: utf-8 -*- +"""Common constants.""" from readthedocs.builds.version_slug import VERSION_SLUG_REGEX from readthedocs.projects.constants import LANGUAGES_REGEX, PROJECT_SLUG_REGEX diff --git a/readthedocs/core/__init__.py b/readthedocs/core/__init__.py index ed1c53deb..c6205d82c 100644 --- a/readthedocs/core/__init__.py +++ b/readthedocs/core/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """App initialization.""" default_app_config = 'readthedocs.core.apps.CoreAppConfig' diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py index 803952a79..aa2fad046 100644 --- a/readthedocs/core/adapters.py +++ b/readthedocs/core/adapters.py @@ -1,4 +1,5 @@ -"""Allauth overrides""" +# -*- coding: utf-8 -*- +"""Allauth overrides.""" import json import logging @@ -19,16 +20,17 @@ log = logging.getLogger(__name__) class AccountAdapter(DefaultAccountAdapter): - """Customize Allauth emails to match our current patterns""" + """Customize Allauth emails to match our current patterns.""" def format_email_subject(self, subject): return force_text(subject) def send_mail(self, template_prefix, email, context): subject = render_to_string( - '{}_subject.txt'.format(template_prefix), context + '{}_subject.txt'.format(template_prefix), + context, ) - subject = " ".join(subject.splitlines()).strip() + subject = ' '.join(subject.splitlines()).strip() subject = self.format_email_subject(subject) # Allauth sends some additional data in the context, remove it if the @@ -41,13 +43,15 @@ class AccountAdapter(DefaultAccountAdapter): removed_keys.append(key) del context[key] if removed_keys: - log.debug('Removed context we were unable to serialize: %s', - removed_keys) + log.debug( + 'Removed context we were unable to serialize: %s', + removed_keys, + ) send_email( recipient=email, subject=subject, template='{}_message.txt'.format(template_prefix), template_html='{}_message.html'.format(template_prefix), - context=context + context=context, ) diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py index 5e8042f12..da3ebfa15 100644 --- a/readthedocs/core/admin.py +++ b/readthedocs/core/admin.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django admin interface for core models.""" from datetime import timedelta @@ -58,8 +59,14 @@ class UserAdminExtra(UserAdmin): """Admin configuration for User.""" - list_display = ('username', 'email', 'first_name', - 'last_name', 'is_staff', 'is_banned') + list_display = ( + 'username', + 'email', + 'first_name', + 'last_name', + 'is_staff', + 'is_banned', + ) list_filter = (UserProjectFilter,) + UserAdmin.list_filter actions = ['ban_user'] inlines = [UserProjectInline] diff --git a/readthedocs/core/apps.py b/readthedocs/core/apps.py index d422684ba..0944efc16 100644 --- a/readthedocs/core/apps.py +++ b/readthedocs/core/apps.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """App configurations for core app.""" from django.apps import AppConfig diff --git a/readthedocs/core/backends.py b/readthedocs/core/backends.py index 64258fe91..381be54dc 100644 --- a/readthedocs/core/backends.py +++ b/readthedocs/core/backends.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Email backends for core app.""" import smtplib @@ -12,8 +13,11 @@ class SSLEmailBackend(EmailBackend): if self.connection: return False try: - self.connection = smtplib.SMTP_SSL(self.host, self.port, - local_hostname=DNS_NAME.get_fqdn()) + self.connection = smtplib.SMTP_SSL( + self.host, + self.port, + local_hostname=DNS_NAME.get_fqdn(), + ) if self.username and self.password: self.connection.login(self.username, self.password) return True diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py index dc136c511..815afbdaa 100644 --- a/readthedocs/core/context_processors.py +++ b/readthedocs/core/context_processors.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Template context processors for core app.""" from django.conf import settings @@ -10,10 +11,14 @@ def readthedocs_processor(request): 'PRODUCTION_DOMAIN': getattr(settings, 'PRODUCTION_DOMAIN', None), 'USE_SUBDOMAINS': getattr(settings, 'USE_SUBDOMAINS', None), 'GLOBAL_ANALYTICS_CODE': getattr(settings, 'GLOBAL_ANALYTICS_CODE'), - 'DASHBOARD_ANALYTICS_CODE': getattr(settings, 'DASHBOARD_ANALYTICS_CODE'), + 'DASHBOARD_ANALYTICS_CODE': getattr( + settings, 'DASHBOARD_ANALYTICS_CODE' + ), 'SITE_ROOT': getattr(settings, 'SITE_ROOT', '') + '/', 'TEMPLATE_ROOT': getattr(settings, 'TEMPLATE_ROOT', '') + '/', - 'DO_NOT_TRACK_ENABLED': getattr(settings, 'DO_NOT_TRACK_ENABLED', False), + 'DO_NOT_TRACK_ENABLED': getattr( + settings, 'DO_NOT_TRACK_ENABLED', False + ), 'USE_PROMOS': getattr(settings, 'USE_PROMOS', False), } return exports diff --git a/readthedocs/core/fields.py b/readthedocs/core/fields.py index 822ad5ca1..1b6d9f994 100644 --- a/readthedocs/core/fields.py +++ b/readthedocs/core/fields.py @@ -1,9 +1,10 @@ -"""Shared model fields and defaults""" +# -*- coding: utf-8 -*- +"""Shared model fields and defaults.""" import binascii import os def default_token(): - """Generate default value for token field""" + """Generate default value for token field.""" return binascii.hexlify(os.urandom(20)).decode() diff --git a/readthedocs/core/fixtures/flag_types.json b/readthedocs/core/fixtures/flag_types.json index afe8ef1cd..52f137405 100644 --- a/readthedocs/core/fixtures/flag_types.json +++ b/readthedocs/core/fixtures/flag_types.json @@ -1,28 +1,28 @@ [ { - "pk": 1, - "model": "flagging.flagtype", + "pk": 1, + "model": "flagging.flagtype", "fields": { - "description": "This item is inappropriate to the purpose of the site", - "slug": "inappropriate", + "description": "This item is inappropriate to the purpose of the site", + "slug": "inappropriate", "title": "Inappropriate" } - }, + }, { - "pk": 2, - "model": "flagging.flagtype", + "pk": 2, + "model": "flagging.flagtype", "fields": { - "description": "This item is spam", - "slug": "spam", + "description": "This item is spam", + "slug": "spam", "title": "Spam" } - }, + }, { - "pk": 3, - "model": "flagging.flagtype", + "pk": 3, + "model": "flagging.flagtype", "fields": { - "description": "These docs are a duplicate of other, official docs, on the site", - "slug": "duplicate", + "description": "These docs are a duplicate of other, official docs, on the site", + "slug": "duplicate", "title": "Duplicate" } } diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py index 66c09e84b..34ebfbd0d 100644 --- a/readthedocs/core/forms.py +++ b/readthedocs/core/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Forms for core app.""" import logging @@ -63,6 +64,7 @@ class UserDeleteForm(forms.ModelForm): class UserAdvertisingForm(forms.ModelForm): + class Meta: model = UserProfile fields = ['allow_ads'] diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py index 3402d1823..e90d8de00 100644 --- a/readthedocs/core/management/commands/archive.py +++ b/readthedocs/core/management/commands/archive.py @@ -1,4 +1,5 @@ -"""Rebuild documentation for all projects""" +# -*- coding: utf-8 -*- +"""Rebuild documentation for all projects.""" import logging import os @@ -20,10 +21,10 @@ class Command(BaseCommand): doc_index = {} os.chdir(settings.DOCROOT) - for directory in glob("*"): + for directory in glob('*'): doc_index[directory] = [] path = os.path.join(directory, 'rtd-builds') - for version in glob(os.path.join(path, "*")): + for version in glob(os.path.join(path, '*')): v = version.replace(path + '/', '') doc_index[directory].append(v) @@ -31,5 +32,6 @@ class Command(BaseCommand): 'doc_index': doc_index, 'MEDIA_URL': settings.MEDIA_URL, } - html = template_loader.get_template('archive/index.html').render(context) + html = template_loader.get_template('archive/index.html' + ).render(context) print(html) diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py index 1a48748a9..64d52ec84 100644 --- a/readthedocs/core/management/commands/clean_builds.py +++ b/readthedocs/core/management/commands/clean_builds.py @@ -1,8 +1,8 @@ -"""Clean up stable build paths per project version""" +# -*- coding: utf-8 -*- +"""Clean up stable build paths per project version.""" import logging from datetime import timedelta -from optparse import make_option from django.core.management.base import BaseCommand from django.db.models import Max @@ -24,24 +24,24 @@ class Command(BaseCommand): dest='days', type='int', default=365, - help='Find builds older than DAYS days, default: 365' + help='Find builds older than DAYS days, default: 365', ) parser.add_argument( '--dryrun', action='store_true', dest='dryrun', - help='Perform dry run on build cleanup' + help='Perform dry run on build cleanup', ) def handle(self, *args, **options): - """Find stale builds and remove build paths""" + """Find stale builds and remove build paths.""" max_date = timezone.now() - timedelta(days=options['days']) - queryset = (Build.objects - .values('project', 'version') - .annotate(max_date=Max('date')) - .filter(max_date__lt=max_date) - .order_by('-max_date')) + queryset = ( + Build.objects.values('project', 'version').annotate( + max_date=Max('date') + ).filter(max_date__lt=max_date).order_by('-max_date') + ) for build in queryset: try: # Get version from build version id, perform sanity check on diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py index 2e2e49176..85530968f 100644 --- a/readthedocs/core/management/commands/import_github.py +++ b/readthedocs/core/management/commands/import_github.py @@ -1,4 +1,5 @@ -"""Resync GitHub project for user""" +# -*- coding: utf-8 -*- +"""Resync GitHub project for user.""" from django.contrib.auth.models import User from django.core.management.base import BaseCommand @@ -13,7 +14,6 @@ class Command(BaseCommand): def handle(self, *args, **options): if args: for slug in args: - for service in GitHubService.for_user( - User.objects.get(username=slug) - ): + for service in GitHubService.for_user(User.objects.get(username=slug + ),): service.sync() diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index 1e42685a5..e3679a526 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- """ -Import a project's programming language from GitHub +Import a project's programming language from GitHub. This builds a basic management command that will set a projects language to the most used one in GitHub. @@ -35,11 +36,8 @@ class Command(BaseCommand): print('Invalid GitHub token, exiting') return - for project in Project.objects.filter( - programming_language__in=['none', '', 'words'] - ).filter( - repo__contains='github' - ): + for project in Project.objects.filter(programming_language__in=[ + 'none', '', 'words'],).filter(repo__contains='github',): user = repo = '' repo_url = project.repo for regex in GITHUB_REGEXS: @@ -65,7 +63,9 @@ class Command(BaseCommand): languages = resp.json() if not languages: continue - sorted_langs = sorted(list(languages.items()), key=lambda x: x[1], reverse=True) + sorted_langs = sorted( + list(languages.items()), key=lambda x: x[1], reverse=True + ) print('Sorted langs: %s ' % sorted_langs) top_lang = sorted_langs[0][0] else: @@ -73,7 +73,8 @@ class Command(BaseCommand): if top_lang in PL_DICT: slug = PL_DICT[top_lang] print('Setting {} to {}'.format(repo_url, slug)) - Project.objects.filter(pk=project.pk).update(programming_language=slug) + Project.objects.filter(pk=project.pk + ).update(programming_language=slug) else: print('Language unknown: %s' % top_lang) cache.set(cache_key, top_lang, 60 * 600) diff --git a/readthedocs/core/management/commands/provision_elasticsearch.py b/readthedocs/core/management/commands/provision_elasticsearch.py index 690a45803..adab81d49 100644 --- a/readthedocs/core/management/commands/provision_elasticsearch.py +++ b/readthedocs/core/management/commands/provision_elasticsearch.py @@ -1,4 +1,5 @@ -"""Provision Elastic Search""" +# -*- coding: utf-8 -*- +"""Provision Elastic Search.""" import logging @@ -20,19 +21,19 @@ class Command(BaseCommand): help = __doc__ def handle(self, *args, **options): - """Provision new ES instance""" + """Provision new ES instance.""" index = Index() index_name = index.timestamped_index() - log.info("Creating indexes..") + log.info('Creating indexes..') index.create_index(index_name) index.update_aliases(index_name) - log.info("Updating mappings..") + log.info('Updating mappings..') proj = ProjectIndex() proj.put_mapping() page = PageIndex() page.put_mapping() sec = SectionIndex() sec.put_mapping() - log.info("Done!") + log.info('Done!') diff --git a/readthedocs/core/management/commands/pull.py b/readthedocs/core/management/commands/pull.py index f0ebe4206..3c965f8b1 100644 --- a/readthedocs/core/management/commands/pull.py +++ b/readthedocs/core/management/commands/pull.py @@ -1,4 +1,5 @@ -"""Trigger build for project slug""" +# -*- coding: utf-8 -*- +"""Trigger build for project slug.""" import logging diff --git a/readthedocs/core/management/commands/reindex_elasticsearch.py b/readthedocs/core/management/commands/reindex_elasticsearch.py index 9d2ab8349..0d552e86e 100644 --- a/readthedocs/core/management/commands/reindex_elasticsearch.py +++ b/readthedocs/core/management/commands/reindex_elasticsearch.py @@ -1,7 +1,7 @@ -"""Reindex Elastic Search indexes""" +# -*- coding: utf-8 -*- +"""Reindex Elastic Search indexes.""" import logging -from optparse import make_option from django.conf import settings from django.core.management.base import BaseCommand, CommandError @@ -23,11 +23,11 @@ class Command(BaseCommand): '-p', dest='project', default='', - help='Project to index' + help='Project to index', ) def handle(self, *args, **options): - """Build/index all versions or a single project's version""" + """Build/index all versions or a single project's version.""" project = options['project'] queryset = Version.objects.all() @@ -36,13 +36,14 @@ class Command(BaseCommand): queryset = queryset.filter(project__slug=project) if not queryset.exists(): raise CommandError( - 'No project with slug: {slug}'.format(slug=project)) - log.info("Building all versions for %s", project) + 'No project with slug: {slug}'.format(slug=project), + ) + log.info('Building all versions for %s', project) elif getattr(settings, 'INDEX_ONLY_LATEST', True): queryset = queryset.filter(slug=LATEST) for version in queryset: - log.info("Reindexing %s", version) + log.info('Reindexing %s', version) try: commit = version.project.vcs_repo(version.slug).commit except: # noqa @@ -51,7 +52,10 @@ class Command(BaseCommand): commit = None try: - update_search(version.pk, commit, - delete_non_commit_files=False) + update_search( + version.pk, + commit, + delete_non_commit_files=False, + ) except Exception as e: log.exception('Reindex failed for %s, %s', version, e) diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py index e8d17a9db..e5782e404 100644 --- a/readthedocs/core/management/commands/set_metadata.py +++ b/readthedocs/core/management/commands/set_metadata.py @@ -1,4 +1,5 @@ -"""Generate metadata for all projects""" +# -*- coding: utf-8 -*- +"""Generate metadata for all projects.""" import logging @@ -19,8 +20,10 @@ class Command(BaseCommand): def handle(self, *args, **options): queryset = Project.objects.all() for p in queryset: - log.info("Generating metadata for %s", p) + log.info('Generating metadata for %s', p) try: - broadcast(type='app', task=tasks.update_static_metadata, args=[p.pk]) + broadcast( + type='app', task=tasks.update_static_metadata, args=[p.pk] + ) except Exception: log.exception('Build failed for %s', p) diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py index 3fda03a41..a7743a740 100644 --- a/readthedocs/core/management/commands/symlink.py +++ b/readthedocs/core/management/commands/symlink.py @@ -1,4 +1,5 @@ -"""Update symlinks for projects""" +# -*- coding: utf-8 -*- +"""Update symlinks for projects.""" import logging @@ -23,7 +24,8 @@ class Command(BaseCommand): if 'all' in projects: pks = Project.objects.values_list('pk', flat=True) else: - pks = Project.objects.filter(slug__in=projects).values_list('pk', flat=True) + pks = Project.objects.filter(slug__in=projects + ).values_list('pk', flat=True) for proj in pks: try: tasks.symlink_project(project_pk=proj) diff --git a/readthedocs/core/management/commands/update_api.py b/readthedocs/core/management/commands/update_api.py index 3a9b44b1c..5b87bd4a5 100644 --- a/readthedocs/core/management/commands/update_api.py +++ b/readthedocs/core/management/commands/update_api.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Build documentation using the API and not hitting a database. @@ -31,6 +32,6 @@ class Command(BaseCommand): for slug in options['projects']: project_data = api.project(slug).get() p = APIProject(**project_data) - log.info("Building %s", p) + log.info('Building %s', p) # pylint: disable=no-value-for-parameter tasks.update_docs_task(p.pk, docker=docker) diff --git a/readthedocs/core/management/commands/update_versions.py b/readthedocs/core/management/commands/update_versions.py index 9cc0c2971..bf066c775 100644 --- a/readthedocs/core/management/commands/update_versions.py +++ b/readthedocs/core/management/commands/update_versions.py @@ -1,4 +1,5 @@ -"""Rebuild documentation for all projects""" +# -*- coding: utf-8 -*- +"""Rebuild documentation for all projects.""" from django.core.management.base import BaseCommand @@ -16,5 +17,5 @@ class Command(BaseCommand): update_docs_task( version.project_id, record=False, - version_pk=version.pk + version_pk=version.pk, ) diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py index 967a4e84a..1a874e120 100644 --- a/readthedocs/core/middleware.py +++ b/readthedocs/core/middleware.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Middleware for core app.""" import logging @@ -7,7 +8,7 @@ from django.contrib.sessions.middleware import SessionMiddleware from django.core.cache import cache from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.http import Http404, HttpResponseBadRequest -from django.urls.base import get_urlconf, set_urlconf +from django.urls.base import set_urlconf from django.utils.deprecation import MiddlewareMixin from django.utils.translation import ugettext_lazy as _ @@ -17,16 +18,16 @@ from readthedocs.projects.models import Domain, Project log = logging.getLogger(__name__) -LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]" +LOG_TEMPLATE = '(Middleware) {msg} [{host}{path}]' SUBDOMAIN_URLCONF = getattr( settings, 'SUBDOMAIN_URLCONF', - 'readthedocs.core.urls.subdomain' + 'readthedocs.core.urls.subdomain', ) SINGLE_VERSION_URLCONF = getattr( settings, 'SINGLE_VERSION_URLCONF', - 'readthedocs.core.urls.single_version' + 'readthedocs.core.urls.single_version', ) @@ -52,7 +53,7 @@ class SubdomainMiddleware(MiddlewareMixin): production_domain = getattr( settings, 'PRODUCTION_DOMAIN', - 'readthedocs.org' + 'readthedocs.org', ) if public_domain is None: @@ -65,10 +66,8 @@ class SubdomainMiddleware(MiddlewareMixin): if len(domain_parts) == len(public_domain.split('.')) + 1: subdomain = domain_parts[0] is_www = subdomain.lower() == 'www' - if not is_www and ( - # Support ports during local dev - public_domain in host or public_domain in full_host - ): + if not is_www and ( # Support ports during local dev + public_domain in host or public_domain in full_host): if not Project.objects.filter(slug=subdomain).exists(): raise Http404(_('Project not found')) request.subdomain = True @@ -77,10 +76,8 @@ class SubdomainMiddleware(MiddlewareMixin): return None # Serve CNAMEs - if (public_domain not in host and - production_domain not in host and - 'localhost' not in host and - 'testserver' not in host): + if (public_domain not in host and production_domain not in host and + 'localhost' not in host and 'testserver' not in host): request.cname = True domains = Domain.objects.filter(domain=host) if domains.count(): @@ -89,18 +86,24 @@ class SubdomainMiddleware(MiddlewareMixin): request.slug = domain.project.slug request.urlconf = SUBDOMAIN_URLCONF request.domain_object = True - log.debug(LOG_TEMPLATE.format( - msg='Domain Object Detected: %s' % domain.domain, - **log_kwargs)) + log.debug( + LOG_TEMPLATE.format( + msg='Domain Object Detected: %s' % domain.domain, + **log_kwargs + ) + ) break if (not hasattr(request, 'domain_object') and 'HTTP_X_RTD_SLUG' in request.META): request.slug = request.META['HTTP_X_RTD_SLUG'].lower() request.urlconf = SUBDOMAIN_URLCONF request.rtdheader = True - log.debug(LOG_TEMPLATE.format( - msg='X-RTD-Slug header detected: %s' % request.slug, - **log_kwargs)) + log.debug( + LOG_TEMPLATE.format( + msg='X-RTD-Slug header detected: %s' % request.slug, + **log_kwargs + ) + ) # Try header first, then DNS elif not hasattr(request, 'domain_object'): try: @@ -109,26 +112,39 @@ class SubdomainMiddleware(MiddlewareMixin): slug = cname_to_slug(host) cache.set(host, slug, 60 * 60) # Cache the slug -> host mapping permanently. - log.info(LOG_TEMPLATE.format( - msg='CNAME cached: {}->{}'.format(slug, host), - **log_kwargs)) + log.info( + LOG_TEMPLATE.format( + msg='CNAME cached: {}->{}'.format(slug, host), + **log_kwargs + ) + ) request.slug = slug request.urlconf = SUBDOMAIN_URLCONF - log.warning(LOG_TEMPLATE.format( - msg='CNAME detected: %s' % request.slug, - **log_kwargs)) + log.warning( + LOG_TEMPLATE.format( + msg='CNAME detected: %s' % request.slug, + **log_kwargs + ) + ) except: # noqa # Some crazy person is CNAMEing to us. 404. - log.warning(LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs)) + log.warning( + LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs) + ) raise Http404(_('Invalid hostname')) # Google was finding crazy www.blah.readthedocs.org domains. # Block these explicitly after trying CNAME logic. if len(domain_parts) > 3 and not settings.DEBUG: # Stop www.fooo.readthedocs.org if domain_parts[0] == 'www': - log.debug(LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs)) + log.debug( + LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs) + ) return HttpResponseBadRequest(_('Invalid hostname')) - log.debug(LOG_TEMPLATE.format(msg='Allowing long domain name', **log_kwargs)) + log.debug( + LOG_TEMPLATE + .format(msg='Allowing long domain name', **log_kwargs) + ) # raise Http404(_('Invalid hostname')) # Normal request. return None @@ -186,8 +202,9 @@ class SingleVersionMiddleware(MiddlewareMixin): host = request.get_host() path = request.get_full_path() log_kwargs = dict(host=host, path=path) - log.debug(LOG_TEMPLATE.format( - msg='Handling single_version request', **log_kwargs) + log.debug( + LOG_TEMPLATE. + format(msg='Handling single_version request', **log_kwargs), ) return None @@ -217,7 +234,7 @@ class ProxyMiddleware(MiddlewareMixin): else: # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs. The # client's IP will be the first one. - real_ip = real_ip.split(",")[0].strip() + real_ip = real_ip.split(',')[0].strip() request.META['REMOTE_ADDR'] = real_ip @@ -229,7 +246,9 @@ class FooterNoSessionMiddleware(SessionMiddleware): This will reduce the size of our session table drastically. """ - IGNORE_URLS = ['/api/v2/footer_html', '/sustainability/view', '/sustainability/click'] + IGNORE_URLS = [ + '/api/v2/footer_html', '/sustainability/view', '/sustainability/click' + ] def process_request(self, request): for url in self.IGNORE_URLS: diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py index 2460daacd..a2cf841ae 100644 --- a/readthedocs/core/mixins.py +++ b/readthedocs/core/mixins.py @@ -1,4 +1,5 @@ -"""Common mixin classes for views""" +# -*- coding: utf-8 -*- +"""Common mixin classes for views.""" from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator @@ -7,7 +8,7 @@ from vanilla import ListView class ListViewWithForm(ListView): - """List view that also exposes a create form""" + """List view that also exposes a create form.""" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index 6f4f019ae..63ac39d10 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Models for the core app.""" import logging @@ -21,7 +22,10 @@ class UserProfile(models.Model): """Additional information about a User.""" user = AutoOneToOneField( - 'auth.User', verbose_name=_('User'), related_name='profile') + 'auth.User', + verbose_name=_('User'), + related_name='profile', + ) whitelisted = models.BooleanField(_('Whitelisted'), default=False) banned = models.BooleanField(_('Banned'), default=False) homepage = models.CharField(_('Homepage'), max_length=100, blank=True) @@ -39,10 +43,13 @@ class UserProfile(models.Model): def __str__(self): return ( ugettext("%(username)s's profile") % - {'username': self.user.username}) + {'username': self.user.username} + ) def get_absolute_url(self): - return reverse('profiles_profile_detail', kwargs={'username': self.user.username}) + return reverse( + 'profiles_profile_detail', kwargs={'username': self.user.username} + ) def get_contribution_details(self): """ diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py index 6af5f2a58..28d01bd7a 100644 --- a/readthedocs/core/permissions.py +++ b/readthedocs/core/permissions.py @@ -1,4 +1,5 @@ -"""Objects for User permission checks""" +# -*- coding: utf-8 -*- +"""Objects for User permission checks.""" from readthedocs.core.utils.extend import SettingsOverrideObject diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index 5e0b27492..10b5f42d5 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """URL resolver for documentation.""" import re @@ -49,9 +50,18 @@ class ResolverBase: /docs//projects// """ - def base_resolve_path(self, project_slug, filename, version_slug=None, - language=None, private=False, single_version=None, - subproject_slug=None, subdomain=None, cname=None): + def base_resolve_path( + self, + project_slug, + filename, + version_slug=None, + language=None, + private=False, + single_version=None, + subproject_slug=None, + subdomain=None, + cname=None, + ): """Resolve a with nothing smart, just filling in the blanks.""" # Only support `/docs/project' URLs outside our normal environment. Normally # the path should always have a subdomain or CNAME domain @@ -70,14 +80,25 @@ class ResolverBase: url += '{language}/{version_slug}/{filename}' return url.format( - project_slug=project_slug, filename=filename, - version_slug=version_slug, language=language, - single_version=single_version, subproject_slug=subproject_slug, + project_slug=project_slug, + filename=filename, + version_slug=version_slug, + language=language, + single_version=single_version, + subproject_slug=subproject_slug, ) - def resolve_path(self, project, filename='', version_slug=None, - language=None, single_version=None, subdomain=None, - cname=None, private=None): + def resolve_path( + self, + project, + filename='', + version_slug=None, + language=None, + single_version=None, + subdomain=None, + cname=None, + private=None, + ): """Resolve a URL with a subset of fields defined.""" cname = cname or project.domains.filter(canonical=True).first() version_slug = version_slug or project.get_default_version() @@ -136,8 +157,10 @@ class ResolverBase: return getattr(settings, 'PRODUCTION_DOMAIN') - def resolve(self, project, require_https=False, filename='', private=None, - **kwargs): + def resolve( + self, project, require_https=False, filename='', private=None, + **kwargs + ): if private is None: version_slug = kwargs.get('version_slug') if version_slug is None: @@ -171,8 +194,8 @@ class ResolverBase: return '{protocol}://{domain}{path}'.format( protocol=protocol, domain=domain, - path=self.resolve_path(project, filename=filename, private=private, - **kwargs), + path=self. + resolve_path(project, filename=filename, private=private, **kwargs), ) def _get_canonical_project(self, project, projects=None): @@ -210,7 +233,7 @@ class ResolverBase: if self._use_subdomain(): project = self._get_canonical_project(project) subdomain_slug = project.slug.replace('_', '-') - return "{}.{}".format(subdomain_slug, public_domain) + return '{}.{}'.format(subdomain_slug, public_domain) def _get_project_custom_domain(self, project): return project.domains.filter(canonical=True).first() @@ -221,7 +244,9 @@ class ResolverBase: version = project.versions.get(slug=version_slug) private = version.privacy_level == PRIVATE except Version.DoesNotExist: - private = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC) == PRIVATE + private = getattr( + settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC + ) == PRIVATE return private def _fix_filename(self, project, filename): @@ -239,17 +264,17 @@ class ResolverBase: if filename: if filename.endswith('/') or filename.endswith('.html'): path = filename - elif project.documentation_type == "sphinx_singlehtml": - path = "index.html#document-" + filename - elif project.documentation_type in ["sphinx_htmldir", "mkdocs"]: - path = filename + "/" + elif project.documentation_type == 'sphinx_singlehtml': + path = 'index.html#document-' + filename + elif project.documentation_type in ['sphinx_htmldir', 'mkdocs']: + path = filename + '/' elif '#' in filename: # do nothing if the filename contains URL fragments path = filename else: - path = filename + ".html" + path = filename + '.html' else: - path = "" + path = '' return path def _use_custom_domain(self, custom_domain): diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index f8fa5f5ad..a4f029c6b 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -90,15 +90,17 @@ def delete_projects_and_organizations(sender, instance, *args, **kwargs): # https://github.com/rtfd/readthedocs.org/pull/4577 # https://docs.djangoproject.com/en/2.1/topics/db/aggregation/#order-of-annotate-and-filter-clauses # noqa projects = ( - Project.objects.annotate(num_users=Count('users')) - .filter(users=instance.id).exclude(num_users__gt=1) + Project.objects.annotate(num_users=Count('users') + ).filter(users=instance.id + ).exclude(num_users__gt=1) ) # Here we count the users list from the organization that the user belong # Then exclude the organizations where there are more than one user oauth_organizations = ( - RemoteOrganization.objects.annotate(num_users=Count('users')) - .filter(users=instance.id).exclude(num_users__gt=1) + RemoteOrganization.objects.annotate(num_users=Count('users') + ).filter(users=instance.id + ).exclude(num_users__gt=1) ) projects.delete() diff --git a/readthedocs/core/static/core/font/fontawesome-webfont.svg b/readthedocs/core/static/core/font/fontawesome-webfont.svg index 855c845e5..52c077335 100644 --- a/readthedocs/core/static/core/font/fontawesome-webfont.svg +++ b/readthedocs/core/static/core/font/fontawesome-webfont.svg @@ -8,7 +8,7 @@ Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index bde0d2d45..b43da937e 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ A class that manages the symlinks for nginx to serve public files. @@ -77,10 +78,12 @@ class Symlink: def __init__(self, project): self.project = project self.project_root = os.path.join( - self.WEB_ROOT, project.slug + self.WEB_ROOT, + project.slug, ) self.subproject_root = os.path.join( - self.project_root, 'projects' + self.project_root, + 'projects', ) self.environment = LocalEnvironment(project) self.sanity_check() @@ -92,9 +95,13 @@ class Symlink: This will leave it in the proper state for the single_project setting. """ if os.path.islink(self.project_root) and not self.project.single_version: - log.info(constants.LOG_TEMPLATE.format( - project=self.project.slug, version='', - msg="Removing single version symlink")) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', + msg='Removing single version symlink', + ) + ) safe_unlink(self.project_root) safe_makedirs(self.project_root) elif (self.project.single_version and @@ -147,13 +154,15 @@ class Symlink: domains = Domain.objects.filter(project=self.project) for dom in domains: log_msg = 'Symlinking CNAME: {} -> {}'.format( - dom.domain, self.project.slug + dom.domain, + self.project.slug, ) log.info( constants.LOG_TEMPLATE.format( project=self.project.slug, - version='', msg=log_msg - ) + version='', + msg=log_msg, + ), ) # CNAME to doc root @@ -162,17 +171,26 @@ class Symlink: # Project symlink project_cname_symlink = os.path.join( - self.PROJECT_CNAME_ROOT, dom.domain + self.PROJECT_CNAME_ROOT, + dom.domain, ) self.environment.run( - 'ln', '-nsf', self.project.doc_path, project_cname_symlink + 'ln', + '-nsf', + self.project.doc_path, + project_cname_symlink, ) def remove_symlink_cname(self, domain): """Remove CNAME symlink.""" - log_msg = "Removing symlink for CNAME {}".format(domain.domain) - log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, - version='', msg=log_msg)) + log_msg = 'Removing symlink for CNAME {}'.format(domain.domain) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', + msg=log_msg, + ) + ) symlink = os.path.join(self.CNAME_ROOT, domain.domain) safe_unlink(symlink) @@ -180,8 +198,7 @@ class Symlink: """ Symlink project subprojects. - Link from $WEB_ROOT/projects/ -> - $WEB_ROOT/ + Link from $WEB_ROOT/projects/ -> $WEB_ROOT/ """ subprojects = set() rels = self.get_subprojects() @@ -198,12 +215,20 @@ class Symlink: from_to[rel.alias] = rel.child.slug subprojects.add(rel.alias) for from_slug, to_slug in list(from_to.items()): - log_msg = "Symlinking subproject: {} -> {}".format(from_slug, to_slug) - log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, - version='', msg=log_msg)) + log_msg = 'Symlinking subproject: {} -> {}'.format( + from_slug, to_slug + ) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', + msg=log_msg, + ) + ) symlink = os.path.join(self.subproject_root, from_slug) docs_dir = os.path.join( - self.WEB_ROOT, to_slug + self.WEB_ROOT, + to_slug, ) symlink_dir = os.sep.join(symlink.split(os.path.sep)[:-1]) if not os.path.lexists(symlink_dir): @@ -215,7 +240,8 @@ class Symlink: if result.exit_code > 0: log.error( 'Could not symlink path: status=%d error=%s', - result.exit_code, result.error + result.exit_code, + result.error, ) # Remove old symlinks @@ -229,7 +255,7 @@ class Symlink: Symlink project translations. Link from $WEB_ROOT/// -> - $WEB_ROOT/// + $WEB_ROOT/// """ translations = {} @@ -249,8 +275,9 @@ class Symlink: log.info( constants.LOG_TEMPLATE.format( project=self.project.slug, - version='', msg=log_msg - ) + version='', + msg=log_msg, + ), ) symlink = os.path.join(self.project_root, language) docs_dir = os.path.join(self.WEB_ROOT, slug, language) @@ -271,7 +298,7 @@ class Symlink: Symlink project single version. Link from $WEB_ROOT/ -> - HOME/user_builds//rtd-builds/latest/ + HOME/user_builds//rtd-builds/latest/ """ version = self.get_default_version() @@ -288,7 +315,7 @@ class Symlink: settings.DOCROOT, self.project.slug, 'rtd-builds', - version.slug + version.slug, ) self.environment.run('ln', '-nsf', docs_dir, symlink) @@ -297,11 +324,13 @@ class Symlink: Symlink project's versions. Link from $WEB_ROOT//// -> - HOME/user_builds//rtd-builds/ + HOME/user_builds//rtd-builds/ """ versions = set() version_dir = os.path.join( - self.WEB_ROOT, self.project.slug, self.project.language + self.WEB_ROOT, + self.project.slug, + self.project.language, ) # Include active public versions, # as well as public versions that are built but not active, for archived versions @@ -315,15 +344,15 @@ class Symlink: constants.LOG_TEMPLATE.format( project=self.project.slug, version='', - msg=log_msg - ) + msg=log_msg, + ), ) symlink = os.path.join(version_dir, version.slug) docs_dir = os.path.join( settings.DOCROOT, self.project.slug, 'rtd-builds', - version.slug + version.slug, ) self.environment.run('ln', '-nsf', docs_dir, symlink) versions.add(version.slug) @@ -346,11 +375,16 @@ class Symlink: class PublicSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'public_web_root') - PROJECT_CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_project') + PROJECT_CNAME_ROOT = os.path.join( + settings.SITE_ROOT, 'public_cname_project' + ) def get_version_queryset(self): - return (self.project.versions.protected(only_active=False).filter(built=True) | - self.project.versions.protected(only_active=True)) + return ( + self.project.versions.protected(only_active=False + ).filter(built=True) | + self.project.versions.protected(only_active=True) + ) def get_subprojects(self): return self.project.subprojects.protected() @@ -362,11 +396,15 @@ class PublicSymlinkBase(Symlink): class PrivateSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'private_web_root') - PROJECT_CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_project') + PROJECT_CNAME_ROOT = os.path.join( + settings.SITE_ROOT, 'private_cname_project' + ) def get_version_queryset(self): - return (self.project.versions.private(only_active=False).filter(built=True) | - self.project.versions.private(only_active=True)) + return ( + self.project.versions.private(only_active=False).filter(built=True) | + self.project.versions.private(only_active=True) + ) def get_subprojects(self): return self.project.subprojects.private() diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py index 446a19b7e..d053614c5 100644 --- a/readthedocs/core/tasks.py +++ b/readthedocs/core/tasks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Basic tasks.""" import logging @@ -18,8 +19,10 @@ EMAIL_TIME_LIMIT = 30 @app.task(queue='web', time_limit=EMAIL_TIME_LIMIT) -def send_email_task(recipient, subject, template, template_html, - context=None, from_email=None, **kwargs): +def send_email_task( + recipient, subject, template, template_html, context=None, + from_email=None, **kwargs +): """ Send multipart email. @@ -43,14 +46,15 @@ def send_email_task(recipient, subject, template, template_html, """ msg = EmailMultiAlternatives( subject, - get_template(template).render(context), - from_email or settings.DEFAULT_FROM_EMAIL, - [recipient], - **kwargs + get_template(template).render(context), from_email or + settings.DEFAULT_FROM_EMAIL, + [recipient], **kwargs ) try: - msg.attach_alternative(get_template(template_html).render(context), - 'text/html') + msg.attach_alternative( + get_template(template_html).render(context), + 'text/html', + ) except TemplateDoesNotExist: pass msg.send() @@ -61,5 +65,7 @@ def send_email_task(recipient, subject, template, template_html, def clear_persistent_messages(): # Delete all expired message_extend's messages log.info("Deleting all expired message_extend's messages") - expired_messages = PersistentMessage.objects.filter(expires__lt=timezone.now()) + expired_messages = PersistentMessage.objects.filter( + expires__lt=timezone.now() + ) expired_messages.delete() diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index 304ca3aff..b4862e525 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,7 +1,8 @@ +# -*- coding: utf-8 -*- """Template tags for core app.""" import hashlib -from urllib.parse import urlparse, urlencode +from urllib.parse import urlencode from django import template from django.conf import settings @@ -19,23 +20,25 @@ register = template.Library() @register.filter def gravatar(email, size=48): """ - Hacked from djangosnippets.org, but basically given an email address + Hacked from djangosnippets.org, but basically given an email address. render an img tag with the hashed up bits needed for leetness omgwtfstillreading """ - url = "http://www.gravatar.com/avatar.php?%s" % urlencode({ + url = 'http://www.gravatar.com/avatar.php?%s' % urlencode({ 'gravatar_id': hashlib.md5(email).hexdigest(), - 'size': str(size) + 'size': str(size), }) - return ('gravatar' % (url, size, size)) + return ( + 'gravatar' % (url, size, size) + ) -@register.simple_tag(name="doc_url") +@register.simple_tag(name='doc_url') def make_document_url(project, version=None, page=''): if not project: - return "" + return '' return resolve(project=project, version_slug=version, filename=page) @@ -48,7 +51,7 @@ def restructuredtext(value, short=False): if settings.DEBUG: raise template.TemplateSyntaxError( "Error in 'restructuredtext' filter: " - "The Python docutils library isn't installed." + "The Python docutils library isn't installed.", ) return force_text(value) else: @@ -56,20 +59,22 @@ def restructuredtext(value, short=False): 'raw_enabled': False, 'file_insertion_enabled': False, } - docutils_settings.update(getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {})) + docutils_settings.update( + getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {}) + ) try: parts = publish_parts( source=force_bytes(value), - writer_name="html4css1", + writer_name='html4css1', settings_overrides=docutils_settings, ) except ApplicationError: return force_text(value) - out = force_text(parts["fragment"]) + out = force_text(parts['fragment']) try: if short: - out = out.split("\n")[0] + out = out.split('\n')[0] except IndexError: pass return mark_safe(out) diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index 814018dc9..fbbe98041 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Template tags to query projects by privacy.""" from django import template @@ -16,6 +17,8 @@ def is_admin(user, project): @register.simple_tag(takes_context=True) def get_public_projects(context, user): - projects = Project.objects.for_user_and_viewer(user=user, viewer=context['request'].user) + projects = Project.objects.for_user_and_viewer( + user=user, viewer=context['request'].user + ) context['public_projects'] = projects return '' diff --git a/readthedocs/core/urls/__init__.py b/readthedocs/core/urls/__init__.py index 48b2e9a61..4c0951e9c 100644 --- a/readthedocs/core/urls/__init__.py +++ b/readthedocs/core/urls/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """URL configuration for core app.""" from __future__ import absolute_import @@ -8,53 +9,77 @@ from readthedocs.core import views from readthedocs.core.views import hooks, serve from readthedocs.projects.feeds import LatestProjectsFeed, NewProjectsFeed - docs_urls = [ - url((r'^docs/(?P{project_slug})/page/' - r'(?P{filename_slug})$'.format(**pattern_opts)), + url( + ( + r'^docs/(?P{project_slug})/page/' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve.redirect_page_with_filename, - name='docs_detail'), - - url((r'^docs/(?P{project_slug})/' - r'(?:|projects/(?P{project_slug})/)$'.format(**pattern_opts)), + name='docs_detail', + ), + url( + ( + r'^docs/(?P{project_slug})/' + r'(?:|projects/(?P{project_slug})/)$'.format( + **pattern_opts + ) + ), serve.redirect_project_slug, - name='docs_detail'), - - url((r'^docs/(?P{project_slug})/' - r'(?:|projects/(?P{project_slug})/)' - r'(?P{lang_slug})/' - r'(?P{version_slug})/' - r'(?P{filename_slug})'.format(**pattern_opts)), + name='docs_detail', + ), + url( + ( + r'^docs/(?P{project_slug})/' + r'(?:|projects/(?P{project_slug})/)' + r'(?P{lang_slug})/' + r'(?P{version_slug})/' + r'(?P{filename_slug})'.format(**pattern_opts) + ), serve.serve_docs, - name='docs_detail'), + name='docs_detail', + ), ] - core_urls = [ # Hooks url(r'^github', hooks.github_build, name='github_build'), url(r'^gitlab', hooks.gitlab_build, name='gitlab_build'), url(r'^bitbucket', hooks.bitbucket_build, name='bitbucket_build'), - url((r'^build/' - r'(?P{project_slug})'.format(**pattern_opts)), + url( + ( + r'^build/' + r'(?P{project_slug})'.format(**pattern_opts) + ), hooks.generic_build, - name='generic_build'), + name='generic_build', + ), # Random other stuff - url(r'^random/(?P{project_slug})'.format(**pattern_opts), + url( + r'^random/(?P{project_slug})'.format(**pattern_opts), views.random_page, - name='random_page'), + name='random_page', + ), url(r'^random/$', views.random_page, name='random_page'), - url((r'^wipe/(?P{project_slug})/' - r'(?P{version_slug})/$'.format(**pattern_opts)), + url( + ( + r'^wipe/(?P{project_slug})/' + r'(?P{version_slug})/$'.format(**pattern_opts) + ), views.wipe_version, - name='wipe_version'), + name='wipe_version', + ), ] deprecated_urls = [ - url(r'^feeds/new/$', + url( + r'^feeds/new/$', NewProjectsFeed(), - name="new_feed"), - url(r'^feeds/latest/$', + name='new_feed', + ), + url( + r'^feeds/latest/$', LatestProjectsFeed(), - name="latest_feed"), + name='latest_feed', + ), ] diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py index b232ef9ae..f8d33a761 100644 --- a/readthedocs/core/urls/single_version.py +++ b/readthedocs/core/urls/single_version.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """URL configuration for a single version.""" from functools import reduce from operator import add @@ -14,33 +15,43 @@ handler500 = 'readthedocs.core.views.server_error_500' handler404 = 'readthedocs.core.views.server_error_404' single_version_urls = [ - url(r'^(?:|projects/(?P{project_slug})/)' + url( + r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), serve.redirect_page_with_filename, - name='docs_detail'), - - url((r'^(?:|projects/(?P{project_slug})/)' - r'(?P{filename_slug})$'.format(**pattern_opts)), + name='docs_detail', + ), + url( + ( + r'^(?:|projects/(?P{project_slug})/)' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve.serve_docs, - name='docs_detail'), + name='docs_detail', + ), ] groups = [single_version_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): - groups.insert(0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)) + groups.insert( + 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + ) # Allow `/docs/` URL's when not using subdomains or during local dev if not getattr(settings, 'USE_SUBDOMAIN', False) or settings.DEBUG: docs_url = [ - url((r'^docs/(?P[-\w]+)/' - r'(?:|projects/(?P{project_slug})/)' - r'(?P{filename_slug})$'.format(**pattern_opts)), + url( + ( + r'^docs/(?P[-\w]+)/' + r'(?:|projects/(?P{project_slug})/)' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve.serve_docs, - name='docs_detail') + name='docs_detail', + ), ] groups.insert(1, docs_url) - urlpatterns = reduce(add, groups) diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 7b5b752fc..cddc49a98 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """URL configurations for subdomains.""" from functools import reduce from operator import add @@ -19,27 +20,37 @@ handler500 = server_error_500 handler404 = server_error_404 subdomain_urls = [ - url(r'^(?:|projects/(?P{project_slug})/)' + url( + r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), redirect_page_with_filename, - name='docs_detail'), - - url((r'^(?:|projects/(?P{project_slug})/)$').format(**pattern_opts), + name='docs_detail', + ), + url( + (r'^(?:|projects/(?P{project_slug})/)$').format( + **pattern_opts + ), redirect_project_slug, - name='redirect_project_slug'), - - url((r'^(?:|projects/(?P{project_slug})/)' - r'(?P{lang_slug})/' - r'(?P{version_slug})/' - r'(?P{filename_slug})$'.format(**pattern_opts)), + name='redirect_project_slug', + ), + url( + ( + r'^(?:|projects/(?P{project_slug})/)' + r'(?P{lang_slug})/' + r'(?P{version_slug})/' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve_docs, - name='docs_detail'), + name='docs_detail', + ), ] groups = [subdomain_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): - groups.insert(0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)) + groups.insert( + 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + ) urlpatterns = reduce(add, groups) diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py index 8632b1557..2123ce5a3 100644 --- a/readthedocs/core/utils/__init__.py +++ b/readthedocs/core/utils/__init__.py @@ -19,7 +19,6 @@ from celery import group, chord from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED from readthedocs.doc_builder.constants import DOCKER_LIMITS - log = logging.getLogger(__name__) SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser()) diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py index ca8e3f40b..96319e691 100644 --- a/readthedocs/core/utils/extend.py +++ b/readthedocs/core/utils/extend.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Patterns for extending Read the Docs.""" import inspect @@ -19,7 +20,7 @@ def get_override_class(proxy_class, default_class=None): default_class = getattr(proxy_class, '_default_class') class_id = '.'.join([ inspect.getmodule(proxy_class).__name__, - proxy_class.__name__ + proxy_class.__name__, ]) class_path = getattr(settings, 'CLASS_OVERRIDES', {}).get(class_id) # pylint: disable=protected-access @@ -32,7 +33,8 @@ def get_override_class(proxy_class, default_class=None): class SettingsOverrideMeta(type): - """Meta class for passing along classmethod class to the underlying class.""" # noqa + """Meta class for passing along classmethod class to the underlying + class.""" # noqa def __getattr__(cls, attr): # noqa: pep8 false positive proxy_class = get_override_class(cls, getattr(cls, '_default_class')) diff --git a/readthedocs/core/utils/tasks/__init__.py b/readthedocs/core/utils/tasks/__init__.py index 344215036..492708ad5 100644 --- a/readthedocs/core/utils/tasks/__init__.py +++ b/readthedocs/core/utils/tasks/__init__.py @@ -1,4 +1,5 @@ -"""Common task exports""" +# -*- coding: utf-8 -*- +"""Common task exports.""" from .permission_checks import user_id_matches # noqa for unused import from .public import PublicTask # noqa diff --git a/readthedocs/core/utils/tasks/permission_checks.py b/readthedocs/core/utils/tasks/permission_checks.py index 1643e8660..4a32e6a1a 100644 --- a/readthedocs/core/utils/tasks/permission_checks.py +++ b/readthedocs/core/utils/tasks/permission_checks.py @@ -1,4 +1,5 @@ -"""Permission checks for tasks""" +# -*- coding: utf-8 -*- +"""Permission checks for tasks.""" __all__ = ('user_id_matches',) diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py index 2c1ccaa9e..2f0bb0114 100644 --- a/readthedocs/core/utils/tasks/public.py +++ b/readthedocs/core/utils/tasks/public.py @@ -1,4 +1,5 @@ -"""Celery tasks with publicly viewable status""" +# -*- coding: utf-8 -*- +"""Celery tasks with publicly viewable status.""" from celery import Task, states from django.conf import settings @@ -7,10 +8,11 @@ from .retrieve import TaskNotFound, get_task_data __all__ = ( - 'PublicTask', 'TaskNoPermission', 'get_public_task_data' + 'PublicTask', + 'TaskNoPermission', + 'get_public_task_data', ) - STATUS_UPDATES_ENABLED = not getattr(settings, 'CELERY_ALWAYS_EAGER', False) @@ -45,7 +47,7 @@ class PublicTask(Task): def set_permission_context(self, context): """ - Set data that can be used by ``check_permission`` to authorize a + Set data that can be used by ``check_permission`` to authorize a. request for the this task. By default it will be the ``kwargs`` passed into the task. @@ -103,22 +105,26 @@ class PublicTask(Task): def my_public_task(user_id): pass """ + def decorator(func): func.check_permission = check return func + return decorator class TaskNoPermission(Exception): + def __init__(self, task_id, *args, **kwargs): message = 'No permission to access task with id {id}'.format( - id=task_id) + id=task_id, + ) super().__init__(message, *args, **kwargs) def get_public_task_data(request, task_id): """ - Return task details as tuple + Return task details as tuple. Will raise `TaskNoPermission` if `request` has no permission to access info of the task with id `task_id`. This is also the case of no task with the diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py index 4c5921169..a2b008d32 100644 --- a/readthedocs/core/utils/tasks/retrieve.py +++ b/readthedocs/core/utils/tasks/retrieve.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Utilities for retrieving task data.""" from celery import states @@ -8,6 +9,7 @@ __all__ = ('TaskNotFound', 'get_task_data') class TaskNotFound(Exception): + def __init__(self, task_id, *args, **kwargs): message = 'No public task found with id {id}'.format(id=task_id) super().__init__(message, *args, **kwargs) @@ -15,7 +17,7 @@ class TaskNotFound(Exception): def get_task_data(task_id): """ - Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task + Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task. meta data has no ``'task_name'`` key set. """ diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 85aec548c..597c2ac62 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -133,13 +133,15 @@ def do_not_track(request): dnt_header = request.META.get('HTTP_DNT') # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation - return JsonResponse({ # pylint: disable=redundant-content-type-for-json-response - 'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html', - 'same-party': [ - 'readthedocs.org', - 'readthedocs.com', - 'readthedocs.io', # .org Documentation Sites - 'readthedocs-hosted.com', # .com Documentation Sites - ], - 'tracking': 'N' if dnt_header == '1' else 'T', - }, content_type='application/tracking-status+json') + return JsonResponse( + { # pylint: disable=redundant-content-type-for-json-response + 'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html', + 'same-party': [ + 'readthedocs.org', + 'readthedocs.com', + 'readthedocs.io', # .org Documentation Sites + 'readthedocs-hosted.com', # .com Documentation Sites + ], + 'tracking': 'N' if dnt_header == '1' else 'T', + }, content_type='application/tracking-status+json', + ) diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index a92640e12..16213e24b 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Views pertaining to builds.""" import json @@ -41,13 +42,14 @@ def _build_version(project, slug, already_built=()): version = project.versions.filter(active=True, slug=slug).first() if version and slug not in already_built: log.info( - "(Version build) Building %s:%s", - project.slug, version.slug, + '(Version build) Building %s:%s', + project.slug, + version.slug, ) trigger_build(project=project, version=version, force=True) return slug - log.info("(Version build) Not Building %s", slug) + log.info('(Version build) Not Building %s', slug) return None @@ -64,8 +66,11 @@ def build_branches(project, branch_list): for branch in branch_list: versions = project.versions_from_branch_name(branch) for version in versions: - log.info("(Branch Build) Processing %s:%s", - project.slug, version.slug) + log.info( + '(Branch Build) Processing %s:%s', + project.slug, + version.slug, + ) ret = _build_version(project, version.slug, already_built=to_build) if ret: to_build.add(ret) @@ -89,9 +94,7 @@ def sync_versions(project): try: version_identifier = project.get_default_branch() version = ( - project.versions - .filter(identifier=version_identifier) - .first() + project.versions.filter(identifier=version_identifier).first() ) if not version: log.info('Unable to sync from %s version', version_identifier) @@ -114,10 +117,13 @@ def get_project_from_url(url): def log_info(project, msg): - log.info(constants.LOG_TEMPLATE - .format(project=project, - version='', - msg=msg)) + log.info( + constants.LOG_TEMPLATE.format( + project=project, + version='', + msg=msg, + ) + ) def _build_url(url, projects, branches): @@ -127,7 +133,7 @@ def _build_url(url, projects, branches): Check each of the ``branches`` to see if they are active and should be built. """ - ret = "" + ret = '' all_built = {} all_not_building = {} @@ -151,14 +157,18 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: msg = '(URL Build) Build Started: {} [{}]'.format( - url, ' '.join(built)) + url, + ' '.join(built), + ) log_info(project_slug, msg=msg) ret += msg for project_slug, not_building in list(all_not_building.items()): if not_building: msg = '(URL Build) Not Building: {} [{}]'.format( - url, ' '.join(not_building)) + url, + ' '.join(not_building), + ) log_info(project_slug, msg=msg) ret += msg @@ -192,7 +202,8 @@ def github_build(request): # noqa: D205 else: data = json.loads(request.body) http_url = data['repository']['url'] - http_search_url = http_url.replace('http://', '').replace('https://', '') + http_search_url = http_url.replace('http://', + '').replace('https://', '') ssh_url = data['repository']['ssh_url'] ssh_search_url = ssh_url.replace('git@', '').replace('.git', '') branches = [data['ref'].replace('refs/heads/', '')] @@ -205,14 +216,14 @@ def github_build(request): # noqa: D205 log.info( 'GitHub webhook search: url=%s branches=%s', http_search_url, - branches + branches, ) ssh_projects = get_project_from_url(ssh_search_url) if ssh_projects: log.info( 'GitHub webhook search: url=%s branches=%s', ssh_search_url, - branches + branches, ) projects = repo_projects | ssh_projects return _build_url(http_search_url, projects, branches) @@ -287,24 +298,26 @@ def bitbucket_build(request): else: data = json.loads(request.body) - version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1 + version = 2 if request.META.get( + 'HTTP_USER_AGENT' + ) == 'Bitbucket-Webhooks/2.0' else 1 if version == 1: - branches = [commit.get('branch', '') - for commit in data['commits']] + branches = [ + commit.get('branch', '') for commit in data['commits'] + ] repository = data['repository'] if not repository['absolute_url']: return HttpResponse('Invalid request', status=400) search_url = 'bitbucket.org{}'.format( - repository['absolute_url'].rstrip('/') + repository['absolute_url'].rstrip('/'), ) elif version == 2: changes = data['push']['changes'] - branches = [change['new']['name'] - for change in changes] + branches = [change['new']['name'] for change in changes] if not data['repository']['full_name']: return HttpResponse('Invalid request', status=400) search_url = 'bitbucket.org/{}'.format( - data['repository']['full_name'] + data['repository']['full_name'], ) except (TypeError, ValueError, KeyError): log.exception('Invalid Bitbucket webhook payload') @@ -352,10 +365,12 @@ def generic_build(request, project_id_or_slug=None): project = Project.objects.get(slug=project_id_or_slug) except (Project.DoesNotExist, ValueError): log.exception( - "(Incoming Generic Build) Repo not found: %s", - project_id_or_slug) + '(Incoming Generic Build) Repo not found: %s', + project_id_or_slug, + ) return HttpResponseNotFound( - 'Repo not found: %s' % project_id_or_slug) + 'Repo not found: %s' % project_id_or_slug, + ) # This endpoint doesn't require authorization, we shouldn't allow builds to # be triggered from this any longer. Deprecation plan is to selectively # allow access to this endpoint for now. @@ -364,8 +379,11 @@ def generic_build(request, project_id_or_slug=None): if request.method == 'POST': slug = request.POST.get('version_slug', project.default_version) log.info( - "(Incoming Generic Build) %s [%s]", project.slug, slug) + '(Incoming Generic Build) %s [%s]', + project.slug, + slug, + ) _build_version(project, slug) else: - return HttpResponse("You must POST to this resource.") + return HttpResponse('You must POST to this resource.') return redirect('builds_project_list', project.slug) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 7f8bb3651..9f87d82ab 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Doc serving from Python. @@ -54,8 +55,11 @@ def map_subproject_slug(view_func): .. warning:: Does not take into account any kind of privacy settings. """ + @wraps(view_func) - def inner_view(request, subproject=None, subproject_slug=None, *args, **kwargs): # noqa + def inner_view( + request, subproject=None, subproject_slug=None, *args, **kwargs + ): # noqa if subproject is None and subproject_slug: # Try to fetch by subproject alias first, otherwise we might end up # redirected to an unrelated project. @@ -81,8 +85,11 @@ def map_project_slug(view_func): .. warning:: Does not take into account any kind of privacy settings. """ + @wraps(view_func) - def inner_view(request, project=None, project_slug=None, *args, **kwargs): # noqa + def inner_view( + request, project=None, project_slug=None, *args, **kwargs + ): # noqa if project is None: if not project_slug: project_slug = request.slug @@ -107,7 +114,8 @@ def redirect_project_slug(request, project, subproject): # pylint: disable=unus def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa """Redirect /page/file.html to /en/latest/file.html.""" return HttpResponseRedirect( - resolve(subproject or project, filename=filename)) + resolve(subproject or project, filename=filename), + ) def _serve_401(request, project): @@ -125,7 +133,8 @@ def _serve_file(request, filename, basepath): # Serve from Nginx content_type, encoding = mimetypes.guess_type( - os.path.join(basepath, filename)) + os.path.join(basepath, filename), + ) content_type = content_type or 'application/octet-stream' response = HttpResponse(content_type=content_type) if encoding: @@ -144,9 +153,15 @@ def _serve_file(request, filename, basepath): @map_project_slug @map_subproject_slug def serve_docs( - request, project, subproject, lang_slug=None, version_slug=None, - filename=''): - """Exists to map existing proj, lang, version, filename views to the file format.""" + request, + project, + subproject, + lang_slug=None, + version_slug=None, + filename='', +): + """Exists to map existing proj, lang, version, filename views to the file + format.""" if not version_slug: version_slug = project.get_default_version() try: @@ -211,4 +226,5 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''): files_tried.append(os.path.join(basepath, filename)) raise Http404( - 'File not found. Tried these files: %s' % ','.join(files_tried)) + 'File not found. Tried these files: %s' % ','.join(files_tried), + ) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 65b35f5f2..805eb0ce5 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ MkDocs_ backend for building docs. @@ -45,7 +46,8 @@ class BaseMkdocs(BaseBuilder): super().__init__(*args, **kwargs) self.old_artifact_path = os.path.join( self.version.project.checkout_path(self.version.slug), - self.build_dir) + self.build_dir, + ) self.root_path = self.version.project.checkout_path(self.version.slug) self.yaml_file = self.get_yaml_config() @@ -65,14 +67,13 @@ class BaseMkdocs(BaseBuilder): else: self.DEFAULT_THEME_NAME = 'mkdocs' - def get_yaml_config(self): """Find the ``mkdocs.yml`` file in the project root.""" mkdoc_path = self.config.mkdocs.configuration if not mkdoc_path: mkdoc_path = os.path.join( self.project.checkout_path(self.version.slug), - 'mkdocs.yml' + 'mkdocs.yml', ) if not os.path.exists(mkdoc_path): return None @@ -85,9 +86,7 @@ class BaseMkdocs(BaseBuilder): Raise BuildEnvironmentError if failed due to syntax errors. """ try: - return yaml.safe_load( - open(self.yaml_file, 'r') - ) + return yaml.safe_load(open(self.yaml_file, 'r'),) except IOError: return { 'site_name': self.version.project.name, @@ -96,10 +95,12 @@ class BaseMkdocs(BaseBuilder): note = '' if hasattr(exc, 'problem_mark'): mark = exc.problem_mark - note = ' (line %d, column %d)' % (mark.line + 1, mark.column + 1) + note = ' (line %d, column %d)' % ( + mark.line + 1, mark.column + 1 + ) raise MkDocsYAMLParseError( 'Your mkdocs.yml could not be loaded, ' - 'possibly due to a syntax error{note}'.format(note=note) + 'possibly due to a syntax error{note}'.format(note=note), ) def append_conf(self, **__): @@ -131,13 +132,13 @@ class BaseMkdocs(BaseBuilder): # of the mkdocs configuration file. docs_path = os.path.join( os.path.dirname(self.yaml_file), - docs_dir + docs_dir, ) # RTD javascript writing rtd_data = self.generate_rtd_data( docs_dir=os.path.relpath(docs_path, self.root_path), - mkdocs_config=user_config + mkdocs_config=user_config, ) with open(os.path.join(docs_path, 'readthedocs-data.js'), 'w') as f: f.write(rtd_data) @@ -156,7 +157,7 @@ class BaseMkdocs(BaseBuilder): # Write the modified mkdocs configuration yaml.safe_dump( user_config, - open(self.yaml_file, 'w') + open(self.yaml_file, 'w'), ) # Write the mkdocs.yml to the build logs @@ -183,13 +184,17 @@ class BaseMkdocs(BaseBuilder): 'programming_language': self.version.project.programming_language, 'page': None, 'theme': self.get_theme_name(mkdocs_config), - 'builder': "mkdocs", + 'builder': 'mkdocs', 'docroot': docs_dir, - 'source_suffix': ".md", - 'api_host': getattr(settings, 'PUBLIC_API_URL', 'https://readthedocs.org'), + 'source_suffix': '.md', + 'api_host': getattr( + settings, 'PUBLIC_API_URL', 'https://readthedocs.org' + ), 'ad_free': not self.project.show_advertising, 'commit': self.version.project.vcs_repo(self.version.slug).commit, - 'global_analytics_code': getattr(settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1'), + 'global_analytics_code': getattr( + settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1' + ), 'user_analytics_code': analytics_code, } data_json = json.dumps(readthedocs_data, indent=4) @@ -210,21 +215,22 @@ class BaseMkdocs(BaseBuilder): self.python_env.venv_bin(filename='mkdocs'), self.builder, '--clean', - '--site-dir', self.build_dir, - '--config-file', self.yaml_file, + '--site-dir', + self.build_dir, + '--config-file', + self.yaml_file, ] if self.config.mkdocs.fail_on_warning: build_command.append('--strict') cmd_ret = self.run( - *build_command, - cwd=checkout_path, + *build_command, cwd=checkout_path, bin_path=self.python_env.venv_bin() ) return cmd_ret.successful def get_theme_name(self, mkdocs_config): """ - Get the theme configuration in the mkdocs_config + Get the theme configuration in the mkdocs_config. In v0.17.0, the theme configuration switched from two separate configs (both optional) to a nested directive. diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index 8402cfd7e..b656433e5 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Sphinx_ backend for building docs. @@ -44,7 +45,7 @@ class BaseSphinx(BaseBuilder): self.config_file = self.project.conf_file(self.version.slug) self.old_artifact_path = os.path.join( os.path.dirname(self.config_file), - self.sphinx_build_dir + self.sphinx_build_dir, ) except ProjectConfigurationError: docs_dir = self.docs_dir() @@ -57,11 +58,13 @@ class BaseSphinx(BaseBuilder): """Create ``conf.py`` if it doesn't exist.""" docs_dir = self.docs_dir() conf_template = render_to_string( - 'sphinx/conf.py.conf', { + 'sphinx/conf.py.conf', + { 'project': self.project, 'version': self.version, 'master_doc': master_doc, - }) + }, + ) conf_file = os.path.join(docs_dir, 'conf.py') safe_write(conf_file, conf_template) @@ -73,25 +76,28 @@ class BaseSphinx(BaseBuilder): os.path.dirname( os.path.relpath( self.config_file, - self.project.checkout_path(self.version.slug) - ) + self.project.checkout_path(self.version.slug), + ), ), '', ) remote_version = self.version.commit_name github_user, github_repo = version_utils.get_github_username_repo( - url=self.project.repo) + url=self.project.repo, + ) github_version_is_editable = (self.version.type == 'branch') display_github = github_user is not None bitbucket_user, bitbucket_repo = version_utils.get_bitbucket_username_repo( # noqa - url=self.project.repo) + url=self.project.repo, + ) bitbucket_version_is_editable = (self.version.type == 'branch') display_bitbucket = bitbucket_user is not None gitlab_user, gitlab_repo = version_utils.get_gitlab_username_repo( - url=self.project.repo) + url=self.project.repo, + ) gitlab_version_is_editable = (self.version.type == 'branch') display_gitlab = gitlab_user is not None @@ -143,7 +149,7 @@ class BaseSphinx(BaseBuilder): # Features 'dont_overwrite_sphinx_context': self.project.has_feature( - Feature.DONT_OVERWRITE_SPHINX_CONTEXT + Feature.DONT_OVERWRITE_SPHINX_CONTEXT, ), } @@ -156,22 +162,22 @@ class BaseSphinx(BaseBuilder): return data def append_conf(self, **__): - """Find or create a ``conf.py`` with a rendered ``doc_builder/conf.py.tmpl`` appended""" + """Find or create a ``conf.py`` with a rendered + ``doc_builder/conf.py.tmpl`` appended.""" if self.config_file is None: master_doc = self.create_index(extension='rst') self._write_config(master_doc=master_doc) try: self.config_file = ( - self.config_file or - self.project.conf_file(self.version.slug) + self.config_file or self.project.conf_file(self.version.slug) ) outfile = codecs.open(self.config_file, encoding='utf-8', mode='a') except (ProjectConfigurationError, IOError): trace = sys.exc_info()[2] raise ProjectConfigurationError( - ProjectConfigurationError.NOT_FOUND - ).with_traceback(trace) + ProjectConfigurationError.NOT_FOUND, + ).with_traceback(trace) # Append config to project conf file tmpl = template_loader.get_template('doc_builder/conf.py.tmpl') @@ -215,8 +221,7 @@ class BaseSphinx(BaseBuilder): self.sphinx_build_dir, ]) cmd_ret = self.run( - *build_command, - cwd=os.path.dirname(self.config_file), + *build_command, cwd=os.path.dirname(self.config_file), bin_path=self.python_env.venv_bin() ) return cmd_ret.successful @@ -235,10 +240,11 @@ class HtmlBuilder(BaseSphinx): # Copy JSON artifacts to its own directory # to keep compatibility with the older builder. json_path = os.path.abspath( - os.path.join(self.old_artifact_path, '..', 'json') + os.path.join(self.old_artifact_path, '..', 'json'), ) json_path_target = self.project.artifact_path( - version=self.version.slug, type_='sphinx_search' + version=self.version.slug, + type_='sphinx_search', ) if os.path.exists(json_path): if os.path.exists(json_path_target): @@ -246,12 +252,10 @@ class HtmlBuilder(BaseSphinx): log.info('Copying json on the local filesystem') shutil.copytree( json_path, - json_path_target + json_path_target, ) else: - log.warning( - 'Not moving json because the build dir is unknown.' - ) + log.warning('Not moving json because the build dir is unknown.',) class HtmlDirBuilder(HtmlBuilder): @@ -297,7 +301,8 @@ class LocalMediaBuilder(BaseSphinx): filename=to_write, arcname=os.path.join( '{}-{}'.format(self.project.slug, self.version.slug), - to_write), + to_write, + ), ) archive.close() @@ -386,11 +391,16 @@ class PdfBuilder(BaseSphinx): # Run LaTeX -> PDF conversions pdflatex_cmds = [ ['pdflatex', '-interaction=nonstopmode', tex_file] - for tex_file in tex_files] # yapf: disable + for tex_file in tex_files + ] # yapf: disable makeindex_cmds = [ - ['makeindex', '-s', 'python.ist', '{}.idx'.format( - os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0])] - for tex_file in tex_files] # yapf: disable + [ + 'makeindex', '-s', 'python.ist', '{}.idx'.format( + os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0], + ), + ] + for tex_file in tex_files + ] # yapf: disable if self.build_env.command_class == DockerBuildCommand: latex_class = DockerLatexBuildCommand @@ -399,15 +409,27 @@ class PdfBuilder(BaseSphinx): pdf_commands = [] for cmd in pdflatex_cmds: cmd_ret = self.build_env.run_command_class( - cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True) + cls=latex_class, + cmd=cmd, + cwd=latex_cwd, + warn_only=True, + ) pdf_commands.append(cmd_ret) for cmd in makeindex_cmds: cmd_ret = self.build_env.run_command_class( - cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True) + cls=latex_class, + cmd=cmd, + cwd=latex_cwd, + warn_only=True, + ) pdf_commands.append(cmd_ret) for cmd in pdflatex_cmds: cmd_ret = self.build_env.run_command_class( - cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True) + cls=latex_class, + cmd=cmd, + cwd=latex_cwd, + warn_only=True, + ) pdf_match = PDF_RE.search(cmd_ret.output) if pdf_match: self.pdf_file_name = pdf_match.group(1).strip() @@ -441,7 +463,9 @@ class PdfBuilder(BaseSphinx): from_file = None if from_file: to_file = os.path.join( - self.target, '{}.pdf'.format(self.project.slug)) + self.target, + '{}.pdf'.format(self.project.slug), + ) self.run( 'mv', '-f', diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py index 8810ab5f7..6b143ca8d 100644 --- a/readthedocs/doc_builder/base.py +++ b/readthedocs/doc_builder/base.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Base classes for Builders.""" import logging @@ -46,7 +47,9 @@ class BaseBuilder: self.config = python_env.config if python_env else None self._force = force self.target = self.project.artifact_path( - version=self.version.slug, type_=self.type) + version=self.version.slug, + type_=self.type, + ) def force(self, **__): """An optional step to force a build even when nothing has changed.""" @@ -67,7 +70,7 @@ class BaseBuilder: shutil.copytree( self.old_artifact_path, self.target, - ignore=shutil.ignore_patterns(*self.ignore_patterns) + ignore=shutil.ignore_patterns(*self.ignore_patterns), ) else: log.warning('Not moving docs, because the build dir is unknown.') @@ -96,10 +99,14 @@ class BaseBuilder: docs_dir = self.docs_dir() index_filename = os.path.join( - docs_dir, 'index.{ext}'.format(ext=extension)) + docs_dir, + 'index.{ext}'.format(ext=extension), + ) if not os.path.exists(index_filename): readme_filename = os.path.join( - docs_dir, 'README.{ext}'.format(ext=extension)) + docs_dir, + 'README.{ext}'.format(ext=extension), + ) if os.path.exists(readme_filename): return 'README' diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py index 9eabeaa0c..092939d96 100644 --- a/readthedocs/doc_builder/config.py +++ b/readthedocs/doc_builder/config.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """An API to load config from a readthedocs.yml file.""" from os import path @@ -30,7 +31,7 @@ def load_yaml_config(version): try: sphinx_configuration = path.join( version.get_conf_py_path(), - 'conf.py' + 'conf.py', ) except ProjectConfigurationError: sphinx_configuration = None @@ -51,7 +52,7 @@ def load_yaml_config(version): 'sphinx_configuration': sphinx_configuration, 'build_image': project.container_image, 'doctype': project.documentation_type, - } + }, } img_settings = DOCKER_IMAGE_SETTINGS.get(img_name, None) if img_settings: diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py index f2f15010b..1abacebba 100644 --- a/readthedocs/doc_builder/constants.py +++ b/readthedocs/doc_builder/constants.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Doc build constants.""" import logging @@ -31,7 +32,9 @@ DOCKER_IMAGE_SETTINGS = getattr(settings, 'DOCKER_IMAGE_SETTINGS', {}) old_config = getattr(settings, 'DOCKER_BUILD_IMAGES', None) if old_config: - log.warning('Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS') + log.warning( + 'Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS' + ) DOCKER_IMAGE_SETTINGS.update(old_config) DOCKER_LIMITS = {'memory': '200m', 'time': 600} diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index cbdff522d..9276e187a 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -84,9 +84,19 @@ class BuildCommand(BuildCommandResultMixin): :param description: a more grokable description of the command being run """ - def __init__(self, command, cwd=None, shell=False, environment=None, - combine_output=True, input_data=None, build_env=None, - bin_path=None, description=None, record_as_success=False): + def __init__( + self, + command, + cwd=None, + shell=False, + environment=None, + combine_output=True, + input_data=None, + build_env=None, + bin_path=None, + description=None, + record_as_success=False, + ): self.command = command self.shell = shell if cwd is None: @@ -301,11 +311,16 @@ class DockerBuildCommand(BuildCommand): # nicer. Sometimes the kernel kills the command and Docker doesn't # not use the specific exit code, so we check if the word `Killed` # is in the last 15 lines of the command's output - killed_in_output = 'Killed' in '\n'.join(self.output.splitlines()[-15:]) - if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and killed_in_output): - self.output += str(_( - '\n\nCommand killed due to excessive memory consumption\n' - )) + killed_in_output = 'Killed' in '\n'.join( + self.output.splitlines()[-15:] + ) + if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and + killed_in_output): + self.output += str( + _( + '\n\nCommand killed due to excessive memory consumption\n', + ) + ) except DockerAPIError: self.exit_code = -1 if self.output is None or not self.output: @@ -323,17 +338,25 @@ class DockerBuildCommand(BuildCommand): install requests<0.8``. This escapes a good majority of those characters. """ - bash_escape_re = re.compile(r"([\t\ \!\"\#\$\&\'\(\)\*\:\;\<\>\?\@" - r"\[\\\]\^\`\{\|\}\~])") + bash_escape_re = re.compile( + r"([\t\ \!\"\#\$\&\'\(\)\*\:\;\<\>\?\@" + r'\[\\\]\^\`\{\|\}\~])', + ) prefix = '' if self.bin_path: prefix += 'PATH={}:$PATH '.format(self.bin_path) - return ("/bin/sh -c 'cd {cwd} && {prefix}{cmd}'" - .format( - cwd=self.cwd, - prefix=prefix, - cmd=(' '.join([bash_escape_re.sub(r'\\\1', part) - for part in self.command])))) + return ( + "/bin/sh -c 'cd {cwd} && {prefix}{cmd}'".format( + cwd=self.cwd, + prefix=prefix, + cmd=( + ' '.join([ + bash_escape_re.sub(r'\\\1', part) + for part in self.command + ]) + ), + ) + ) class BaseEnvironment: @@ -359,7 +382,8 @@ class BaseEnvironment: def run_command_class( self, cls, cmd, record=None, warn_only=False, - record_as_success=False, **kwargs): + record_as_success=False, **kwargs + ): """ Run command from this environment. @@ -415,11 +439,13 @@ class BaseEnvironment: msg += ':\n{out}'.format(out=build_cmd.output) if warn_only: - log.warning(LOG_TEMPLATE.format( - project=self.project.slug, - version='latest', - msg=msg, - )) + log.warning( + LOG_TEMPLATE.format( + project=self.project.slug, + version='latest', + msg=msg, + ) + ) else: raise BuildEnvironmentWarning(msg) return build_cmd @@ -475,8 +501,16 @@ class BuildEnvironment(BaseEnvironment): MkDocsYAMLParseError, ) - def __init__(self, project=None, version=None, build=None, config=None, - record=True, environment=None, update_on_success=True): + def __init__( + self, + project=None, + version=None, + build=None, + config=None, + record=True, + environment=None, + update_on_success=True, + ): super().__init__(project, environment) self.version = version self.build = build @@ -563,23 +597,30 @@ class BuildEnvironment(BaseEnvironment): @property def successful(self): - """Is build completed, without top level failures or failing commands.""" # noqa - return (self.done and self.failure is None and - all(cmd.successful for cmd in self.commands)) + """Is build completed, without top level failures or failing + commands.""" # noqa + return ( + self.done and self.failure is None and + all(cmd.successful for cmd in self.commands) + ) @property def failed(self): """Is build completed, but has top level failure or failing commands.""" - return (self.done and ( - self.failure is not None or - any(cmd.failed for cmd in self.commands) - )) + return ( + self.done and ( + self.failure is not None or + any(cmd.failed for cmd in self.commands) + ) + ) @property def done(self): """Is build in finished state.""" - return (self.build is not None and - self.build['state'] == BUILD_STATE_FINISHED) + return ( + self.build is not None and + self.build['state'] == BUILD_STATE_FINISHED + ) def update_build(self, state=None): """ @@ -627,10 +668,10 @@ class BuildEnvironment(BaseEnvironment): # BuildEnvironmentError if not isinstance( self.failure, - ( - BuildEnvironmentException, - BuildEnvironmentWarning, - ), + ( + BuildEnvironmentException, + BuildEnvironmentWarning, + ), ): log.error( 'Build failed with unhandled exception: %s', @@ -668,7 +709,7 @@ class BuildEnvironment(BaseEnvironment): if update_build: try: api_v2.build(self.build['id']).put(self.build) - except HttpClientError as e: + except HttpClientError: log.exception( 'Unable to update build: id=%d', self.build['id'], @@ -751,10 +792,11 @@ class DockerBuildEnvironment(BuildEnvironment): project=self.project.slug, version=self.version.slug, msg=( - 'Removing stale container {}' - .format(self.container_id) + 'Removing stale container {}'.format( + self.container_id + ) ), - ) + ), ) client = self.get_client() client.remove_container(self.container_id) @@ -826,7 +868,7 @@ class DockerBuildEnvironment(BuildEnvironment): version=DOCKER_VERSION, ) return self.client - except DockerException as e: + except DockerException: log.exception( LOG_TEMPLATE.format( project=self.project.slug, @@ -924,10 +966,10 @@ class DockerBuildEnvironment(BuildEnvironment): ) elif state.get('Error'): self.failure = BuildEnvironmentError(( - _('Build exited due to unknown error: {0}') - .format(state.get('Error')) - ), - ) + _('Build exited due to unknown error: {0}').format( + state.get('Error') + ) + ),) def create_container(self): """Create docker container.""" @@ -952,7 +994,7 @@ class DockerBuildEnvironment(BuildEnvironment): environment=self.environment, ) client.start(container=self.container_id) - except ConnectionError as e: + except ConnectionError: log.exception( LOG_TEMPLATE.format( project=self.project.slug, diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 5898dfce0..ad6a458c8 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Exceptions raised when building documentation.""" from django.utils.translation import ugettext_noop @@ -9,7 +10,9 @@ class BuildEnvironmentException(Exception): status_code = None def __init__(self, message=None, **kwargs): - self.status_code = kwargs.pop('status_code', None) or self.status_code or 1 + self.status_code = kwargs.pop( + 'status_code', None + ) or self.status_code or 1 message = message or self.get_default_message() super().__init__(message, **kwargs) diff --git a/readthedocs/doc_builder/loader.py b/readthedocs/doc_builder/loader.py index 8d733b3d8..b878009d2 100644 --- a/readthedocs/doc_builder/loader.py +++ b/readthedocs/doc_builder/loader.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Lookup tables for builders and backends.""" from importlib import import_module @@ -6,11 +7,19 @@ from django.conf import settings # Managers mkdocs = import_module( - getattr(settings, 'MKDOCS_BACKEND', - 'readthedocs.doc_builder.backends.mkdocs')) + getattr( + settings, + 'MKDOCS_BACKEND', + 'readthedocs.doc_builder.backends.mkdocs', + ), +) sphinx = import_module( - getattr(settings, 'SPHINX_BACKEND', - 'readthedocs.doc_builder.backends.sphinx')) + getattr( + settings, + 'SPHINX_BACKEND', + 'readthedocs.doc_builder.backends.sphinx', + ), +) BUILDER_BY_NAME = { # Possible HTML Builders diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index 92ec9a2f5..76b851880 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """An abstraction over virtualenv and Conda environments.""" import copy @@ -40,24 +41,29 @@ class PythonEnvironment: # Handle deleting old build dir build_dir = os.path.join( self.venv_path(), - 'build') + 'build', + ) if os.path.exists(build_dir): - log.info(LOG_TEMPLATE.format( - project=self.project.slug, - version=self.version.slug, - msg='Removing existing build directory', - )) + log.info( + LOG_TEMPLATE.format( + project=self.project.slug, + version=self.version.slug, + msg='Removing existing build directory', + ) + ) shutil.rmtree(build_dir) def delete_existing_venv_dir(self): venv_dir = self.venv_path() # Handle deleting old venv dir if os.path.exists(venv_dir): - log.info(LOG_TEMPLATE.format( - project=self.project.slug, - version=self.version.slug, - msg='Removing existing venv directory', - )) + log.info( + LOG_TEMPLATE.format( + project=self.project.slug, + version=self.version.slug, + msg='Removing existing venv directory', + ) + ) shutil.rmtree(venv_dir) def install_package(self): @@ -66,7 +72,7 @@ class PythonEnvironment: extra_req_param = '' if self.config.python.extra_requirements: extra_req_param = '[{}]'.format( - ','.join(self.config.python.extra_requirements) + ','.join(self.config.python.extra_requirements), ) self.build_env.run( 'python', @@ -134,7 +140,9 @@ class PythonEnvironment: with open(self.environment_json_path(), 'r') as fpath: environment_conf = json.load(fpath) except (IOError, TypeError, KeyError, ValueError): - log.warning('Unable to read/parse readthedocs-environment.json file') + log.warning( + 'Unable to read/parse readthedocs-environment.json file' + ) # We remove the JSON file here to avoid cycling over time with a # corrupted file. os.remove(self.environment_json_path()) @@ -168,7 +176,8 @@ class PythonEnvironment: ]) def save_environment_json(self): - """Save on disk Python and build image versions used to create the venv.""" + """Save on disk Python and build image versions used to create the + venv.""" data = { 'python': { 'version': self.config.python_full_version, @@ -233,9 +242,7 @@ class Virtualenv(PythonEnvironment): # so it is used when installing the other requirements. cmd = pip_install_cmd + ['pip'] self.build_env.run( - *cmd, - bin_path=self.venv_bin(), - cwd=self.checkout_path + *cmd, bin_path=self.venv_bin(), cwd=self.checkout_path ) requirements = [ @@ -267,7 +274,7 @@ class Virtualenv(PythonEnvironment): negative='sphinx<1.8', ), 'sphinx-rtd-theme<0.5', - 'readthedocs-sphinx-ext<0.6' + 'readthedocs-sphinx-ext<0.6', ]) cmd = copy.copy(pip_install_cmd) @@ -288,8 +295,10 @@ class Virtualenv(PythonEnvironment): requirements_file_path = self.config.python.requirements if not requirements_file_path and requirements_file_path != '': builder_class = get_builder_class(self.config.doctype) - docs_dir = (builder_class(build_env=self.build_env, python_env=self) - .docs_dir()) + docs_dir = ( + builder_class(build_env=self.build_env, + python_env=self).docs_dir() + ) paths = [docs_dir, ''] req_files = ['pip_requirements.txt', 'requirements.txt'] for path, req_file in itertools.product(paths, req_files): @@ -337,11 +346,13 @@ class Conda(PythonEnvironment): if os.path.exists(version_path): # Re-create conda directory each time to keep fresh state - log.info(LOG_TEMPLATE.format( - project=self.project.slug, - version=self.version.slug, - msg='Removing existing conda directory', - )) + log.info( + LOG_TEMPLATE.format( + project=self.project.slug, + version=self.version.slug, + msg='Removing existing conda directory', + ) + ) shutil.rmtree(version_path) self.build_env.run( 'conda', diff --git a/readthedocs/doc_builder/signals.py b/readthedocs/doc_builder/signals.py index 08991fabb..9911ba3ab 100644 --- a/readthedocs/doc_builder/signals.py +++ b/readthedocs/doc_builder/signals.py @@ -1,8 +1,9 @@ -"""Signals for adding custom context data""" +# -*- coding: utf-8 -*- +"""Signals for adding custom context data.""" import django.dispatch finalize_sphinx_context_data = django.dispatch.Signal( - providing_args=['buildenv', 'context', 'response_data'] + providing_args=['buildenv', 'context', 'response_data'], ) diff --git a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl index 4dda93914..29ab61b0e 100644 --- a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl @@ -6,7 +6,7 @@ var doc_slug = "{{ slug }}"; var page_name = "{{ pagename }}"; var html_theme = "{{ html_theme }}"; -// mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't +// mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't // available on all pages (e.g. missing in search result) if (typeof mkdocs_page_input_path !== "undefined") { READTHEDOCS_DATA["page"] = mkdocs_page_input_path.substr( diff --git a/readthedocs/gold/__init__.py b/readthedocs/gold/__init__.py index b26c8ed7c..b7fcb9ea1 100644 --- a/readthedocs/gold/__init__.py +++ b/readthedocs/gold/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ A Django app for Gold Membership. diff --git a/readthedocs/gold/admin.py b/readthedocs/gold/admin.py index a1dc3e80e..020d2be94 100644 --- a/readthedocs/gold/admin.py +++ b/readthedocs/gold/admin.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django admin configuration for the Gold Membership app.""" from django.contrib import admin diff --git a/readthedocs/gold/apps.py b/readthedocs/gold/apps.py index 380a21f09..5891264c8 100644 --- a/readthedocs/gold/apps.py +++ b/readthedocs/gold/apps.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Django app configuration for the Gold Membership app.""" from django.apps import AppConfig diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index 31c4a18aa..4840745d8 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Gold subscription forms.""" from django import forms @@ -28,9 +29,11 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): required=True, min_length=4, max_length=4, - widget=forms.HiddenInput(attrs={ - 'data-bind': 'valueInit: last_4_card_digits, value: last_4_card_digits', - }) + widget=forms.HiddenInput( + attrs={ + 'data-bind': 'valueInit: last_4_card_digits, value: last_4_card_digits', + } + ), ) level = forms.ChoiceField( @@ -50,7 +53,8 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): def get_customer_kwargs(self): data = { - 'description': self.customer.get_full_name() or self.customer.username, + 'description': self.customer.get_full_name() or + self.customer.username, 'email': self.customer.email, 'id': self.instance.stripe_id or None, } @@ -78,7 +82,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): # Add a new subscription subscription = customer.subscriptions.create( plan=self.cleaned_data['level'], - source=self.cleaned_data['stripe_token'] + source=self.cleaned_data['stripe_token'], ) return subscription @@ -87,7 +91,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): class GoldProjectForm(forms.Form): project = forms.ChoiceField( required=True, - help_text='Select a project.' + help_text='Select a project.', ) def __init__(self, active_user, *args, **kwargs): @@ -114,4 +118,6 @@ class GoldProjectForm(forms.Form): if self.projects.count() < self.user.num_supported_projects: return cleaned_data - self.add_error(None, 'You already have the max number of supported projects.') + self.add_error( + None, 'You already have the max number of supported projects.' + ) diff --git a/readthedocs/gold/signals.py b/readthedocs/gold/signals.py index 4c089ebb0..155230f38 100644 --- a/readthedocs/gold/signals.py +++ b/readthedocs/gold/signals.py @@ -1,4 +1,5 @@ -"""Gold model signals""" +# -*- coding: utf-8 -*- +"""Gold model signals.""" from django.db.models.signals import pre_delete from django.dispatch import receiver @@ -10,6 +11,6 @@ from .models import GoldUser @receiver(pre_delete, sender=GoldUser) def delete_customer(sender, instance, **__): - """On Gold subscription deletion, remove the customer from Stripe""" + """On Gold subscription deletion, remove the customer from Stripe.""" if sender == GoldUser and instance.stripe_id is not None: utils.delete_customer(instance.stripe_id) diff --git a/readthedocs/gold/templates/gold/projects.html b/readthedocs/gold/templates/gold/projects.html index 682b7bf3c..e832b53ee 100644 --- a/readthedocs/gold/templates/gold/projects.html +++ b/readthedocs/gold/templates/gold/projects.html @@ -48,4 +48,3 @@ Gold Projects {% endblock %} - diff --git a/readthedocs/gold/templates/gold/subscription_form.html b/readthedocs/gold/templates/gold/subscription_form.html index 9dc1ae592..241d7ddd2 100644 --- a/readthedocs/gold/templates/gold/subscription_form.html +++ b/readthedocs/gold/templates/gold/subscription_form.html @@ -79,7 +79,7 @@ $(document).ready(function () {

You are currently using {{ domains.count }} domains: - +

+ {% else %}
    diff --git a/readthedocs/templates/core/widesearchbar.html b/readthedocs/templates/core/widesearchbar.html index c378d92f5..d310e94b5 100644 --- a/readthedocs/templates/core/widesearchbar.html +++ b/readthedocs/templates/core/widesearchbar.html @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/readthedocs/templates/dnt-policy.txt b/readthedocs/templates/dnt-policy.txt index ad946d1f8..458a36d03 100644 --- a/readthedocs/templates/dnt-policy.txt +++ b/readthedocs/templates/dnt-policy.txt @@ -1,4 +1,4 @@ -Do Not Track Compliance Policy +Do Not Track Compliance Policy Version 1.0 @@ -32,23 +32,23 @@ When this domain receives Web requests from a user who enables DNT by actively choosing an opt-out setting in their browser or by installing software that is primarily designed to protect privacy ("DNT User"), we will take the following measures with respect to those users' data, subject to the Exceptions, also -listed below: +listed below: -1. END USER IDENTIFIERS: +1. END USER IDENTIFIERS: a. If a DNT User has logged in to our service, all user identifiers, such as - unique or nearly unique cookies, "supercookies" and fingerprints are - discarded as soon as the HTTP(S) response is issued. + unique or nearly unique cookies, "supercookies" and fingerprints are + discarded as soon as the HTTP(S) response is issued. Data structures which associate user identifiers with accounts may be employed to recognize logged in users per Exception 4 below, but may not be associated with records of the user's activities unless otherwise excepted. - b. If a DNT User is not logged in to our service, we will take steps to ensure - that no user identifiers are transmitted to us at all. + b. If a DNT User is not logged in to our service, we will take steps to ensure + that no user identifiers are transmitted to us at all. -2. LOG RETENTION: +2. LOG RETENTION: a. Logs with DNT Users' identifiers removed (but including IP addresses and User Agent strings) may be retained for a period of 10 days or less, @@ -58,13 +58,13 @@ listed below: and performance problems; and that security and data aggregation systems have time to operate. - b. These logs will not be used for any other purposes. + b. These logs will not be used for any other purposes. -3. OTHER DOMAINS: +3. OTHER DOMAINS: a. If this domain transfers identifiable user data about DNT Users to contractors, affiliates or other parties, or embeds from or posts data to - other domains, we will either: + other domains, we will either: b. ensure that the operators of those domains abide by this policy overall by posting it at /.well-known/dnt-policy.txt via HTTPS on the domains in @@ -75,7 +75,7 @@ listed below: ensure that the recipient's policies and practices require the recipient to respect the policy for our DNT Users' data. - OR + OR obtain a contractual commitment from the recipient to respect this policy for our DNT Users' data. @@ -88,14 +88,14 @@ listed below: c. "Identifiable" means any records which are not Anonymized or otherwise covered by the Exceptions below. -4. PERIODIC REASSERTION OF COMPLIANCE: +4. PERIODIC REASSERTION OF COMPLIANCE: At least once every 12 months, we will take reasonable steps commensurate with the size of our organization and the nature of our service to confirm our ongoing compliance with this document, and we will publicly reassert our compliance. -5. USER NOTIFICATION: +5. USER NOTIFICATION: a. If we are required by law to retain or disclose user identifiers, we will attempt to provide the users with notice (unless we are prohibited or it @@ -105,7 +105,7 @@ listed below: b. We will attempt to provide this notice by email, if the users have given us an email address, and by postal mail if the users have provided a - postal address. + postal address. c. If the users do not challenge the disclosure request, we may be legally required to turn over their information. @@ -120,17 +120,17 @@ EXCEPTIONS Data from DNT Users collected by this domain may be logged or retained only in the following specific situations: -1. CONSENT / "OPT BACK IN" +1. CONSENT / "OPT BACK IN" a. DNT Users are opting out from tracking across the Web. It is possible that for some feature or functionality, we will need to ask a DNT User to - "opt back in" to be tracked by us across the entire Web. + "opt back in" to be tracked by us across the entire Web. b. If we do that, we will take reasonable steps to verify that the users who select this option have genuinely intended to opt back in to tracking. One way to do this is by performing scientifically reasonable user studies with a representative sample of our users, but smaller - organizations can satisfy this requirement by other means. + organizations can satisfy this requirement by other means. c. Where we believe that we have opt back in consent, our server will send a tracking value status header "Tk: C" as described in section 6.2 @@ -138,7 +138,7 @@ the following specific situations: http://www.w3.org/TR/tracking-dnt/#tracking-status-value -2. TRANSACTIONS +2. TRANSACTIONS If a DNT User actively and knowingly enters a transaction with our services (for instance, clicking on a clearly-labeled advertisement, @@ -151,19 +151,19 @@ the following specific situations: item will be shipped. By their nature, some transactions will require data to be retained indefinitely. -3. TECHNICAL AND SECURITY LOGGING: +3. TECHNICAL AND SECURITY LOGGING: a. If, during the processing of the initial request (for unique identifiers) or during the subsequent 10 days (for IP addresses and User Agent strings), we obtain specific information that causes our employees or systems to believe that a request is, or is likely to be, part of a security attack, - spam submission, or fraudulent transaction, then logs of those requests - are not subject to this policy. + spam submission, or fraudulent transaction, then logs of those requests + are not subject to this policy. b. If we encounter technical problems with our site, then, in rare circumstances, we may retain logs for longer than 10 days, if that is necessary to diagnose and fix those problems, but this practice will not be - routinized and we will strive to delete such logs as soon as possible. + routinized and we will strive to delete such logs as soon as possible. 4. AGGREGATION: @@ -179,13 +179,13 @@ the following specific situations: that the dataset, plus any additional information that is in our possession or likely to be available to us, does not allow the reconstruction of reading habits, online or offline activity of groups of - fewer than 5000 individuals or devices. + fewer than 5000 individuals or devices. c. If we generate anonymized datasets under this exception we will publicly document our anonymization methods in sufficient detail to allow outside experts to evaluate the effectiveness of those methods. -5. ERRORS: +5. ERRORS: From time to time, there may be errors by which user data is temporarily logged or retained in violation of this policy. If such errors are diff --git a/readthedocs/templates/flagging/flag_confirm.html b/readthedocs/templates/flagging/flag_confirm.html index ff584e855..788b0c32d 100644 --- a/readthedocs/templates/flagging/flag_confirm.html +++ b/readthedocs/templates/flagging/flag_confirm.html @@ -16,7 +16,7 @@ {% load flagging %}

    {% trans "What you are saying:" %}

    {{ flag_type.description }}

    - +
    {% csrf_token %}

    {% blocktrans %}Are you sure you want to flag "{{ object }}" as "{{ flag_type.title }}"?{% endblocktrans %}

    diff --git a/readthedocs/templates/flagging/flag_success.html b/readthedocs/templates/flagging/flag_success.html index d1cdff28e..e8ab7fd04 100644 --- a/readthedocs/templates/flagging/flag_success.html +++ b/readthedocs/templates/flagging/flag_success.html @@ -14,6 +14,6 @@ {% block content %}

    {% blocktrans %}You have successfully flagged "{{ object }}" as "{{ flag_type.title }}"{% endblocktrans %}

    - +

    {% trans "Thank you for contributing to the quality of this site!" %}

    {% endblock %} diff --git a/readthedocs/templates/projects/domain_confirm_delete.html b/readthedocs/templates/projects/domain_confirm_delete.html index 8781f45a8..c9aeef2ac 100644 --- a/readthedocs/templates/projects/domain_confirm_delete.html +++ b/readthedocs/templates/projects/domain_confirm_delete.html @@ -19,5 +19,3 @@
    {% endblock %} - - diff --git a/readthedocs/templates/projects/domain_form.html b/readthedocs/templates/projects/domain_form.html index 307e089a3..ed4688751 100644 --- a/readthedocs/templates/projects/domain_form.html +++ b/readthedocs/templates/projects/domain_form.html @@ -41,4 +41,3 @@

    {% endblock %} - diff --git a/readthedocs/templates/projects/domain_list.html b/readthedocs/templates/projects/domain_list.html index eab3abb58..be4290e41 100644 --- a/readthedocs/templates/projects/domain_list.html +++ b/readthedocs/templates/projects/domain_list.html @@ -33,7 +33,7 @@

{% endif %} - +

{% trans "Add new Domain" %}

{% csrf_token %} {{ form.as_p }} @@ -42,4 +42,3 @@

{% endblock %} - diff --git a/readthedocs/templates/projects/project_analytics.html b/readthedocs/templates/projects/project_analytics.html index 5ca9ab7d8..bf682e90e 100644 --- a/readthedocs/templates/projects/project_analytics.html +++ b/readthedocs/templates/projects/project_analytics.html @@ -64,7 +64,7 @@ $(document).ready(function () {
    {% for page, count in page_list %}
  • - {{ page }} + {{ page }} {{ count }} ({{ analytics.scaled_page|key:page }}%)
  • @@ -77,7 +77,7 @@ $(document).ready(function () {
      {% for version, count in version_list %}
    • - {{ version }} + {{ version }} {{ count }} ({{ analytics.scaled_version|key:version }}%)
    • diff --git a/readthedocs/templates/projects/project_version_list.html b/readthedocs/templates/projects/project_version_list.html index a83273fe5..f85f3be0d 100644 --- a/readthedocs/templates/projects/project_version_list.html +++ b/readthedocs/templates/projects/project_version_list.html @@ -113,7 +113,7 @@ Versions {% endblock inactive-versions %} - + {% endfor %}
    diff --git a/readthedocs/templates/search/elastic_search.html b/readthedocs/templates/search/elastic_search.html index b14ad50b2..387bf85aa 100644 --- a/readthedocs/templates/search/elastic_search.html +++ b/readthedocs/templates/search/elastic_search.html @@ -68,7 +68,7 @@ {{ name }} {% else %} {{ name }} - {% endif %} + {% endif %} ({{ count }}) @@ -86,7 +86,7 @@ {{ name }} {% else %} {{ name }} - {% endif %} + {% endif %} ({{ count }}) @@ -96,7 +96,7 @@ {% endif %} {% endif %} - + {% block sponsor %}
    Search is sponsored by Elastic, and hosted on Elastic Cloud. @@ -144,7 +144,7 @@ Search is sponsored by Elastic, and hosted {% for result in results.hits.hits %}
  • - {% if result.fields.name %} + {% if result.fields.name %} {# Project #} {{ result.fields.name }} diff --git a/readthedocs/templates/style_catalog.html b/readthedocs/templates/style_catalog.html index 68ab9b3e8..78e2123be 100644 --- a/readthedocs/templates/style_catalog.html +++ b/readthedocs/templates/style_catalog.html @@ -8,7 +8,7 @@

    Header 2.

    Header 3.

    Header 4.

    -
    Header 5.
    +
    Header 5.

    Paragraph. Aside.

    Paragraph with link.

    Paragraph with highlighted text.

    @@ -41,7 +41,7 @@ It's free and simple. Read the Getting Started guide to get goin
  • Ordered list item.
  • -
    +
    @@ -130,18 +130,18 @@ It's free and simple. Read the Getting Started guide to get goin
  • Module list item with menu.
  • Module list item with menu and right content. Right-aligned-ish content.
  • - +
  • diff --git a/readthedocs/urls.py b/readthedocs/urls.py index ba6e67ef3..de18fcd3f 100644 --- a/readthedocs/urls.py +++ b/readthedocs/urls.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # pylint: disable=missing-docstring import os from functools import reduce @@ -42,8 +43,11 @@ basic_urls = [ url(r'^$', HomepageView.as_view(), name='homepage'), url(r'^support/', SupportView.as_view(), name='support'), url(r'^security/', TemplateView.as_view(template_name='security.html')), - url(r'^\.well-known/security.txt$', - TemplateView.as_view(template_name='security.txt', content_type='text/plain')), + url( + r'^\.well-known/security.txt$', + TemplateView + .as_view(template_name='security.txt', content_type='text/plain'), + ), ] rtd_urls = [ @@ -69,7 +73,10 @@ project_urls = [ api_urls = [ url(r'^api/', include(v1_api.urls)), url(r'^api/v2/', include('readthedocs.restapi.urls')), - url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), + url( + r'^api-auth/', + include('rest_framework.urls', namespace='rest_framework') + ), ] i18n_urls = [ @@ -84,8 +91,11 @@ dnt_urls = [ url(r'^\.well-known/dnt/$', do_not_track), # https://github.com/EFForg/dnt-guide#12-how-to-assert-dnt-compliance - url(r'^\.well-known/dnt-policy.txt$', - TemplateView.as_view(template_name='dnt-policy.txt', content_type='text/plain')), + url( + r'^\.well-known/dnt-policy.txt$', + TemplateView + .as_view(template_name='dnt-policy.txt', content_type='text/plain'), + ), ] debug_urls = [] @@ -95,16 +105,24 @@ for build_format in ('epub', 'htmlzip', 'json', 'pdf'): document_root=os.path.join(settings.MEDIA_ROOT, build_format), ) debug_urls += [ - url('style-catalog/$', - TemplateView.as_view(template_name='style_catalog.html')), + url( + 'style-catalog/$', + TemplateView.as_view(template_name='style_catalog.html'), + ), # This must come last after the build output files - url(r'^media/(?P.+)$', - RedirectView.as_view(url=settings.STATIC_URL + '%(remainder)s'), name='media-redirect'), + url( + r'^media/(?P.+)$', + RedirectView.as_view(url=settings.STATIC_URL + '%(remainder)s'), + name='media-redirect', + ), ] # Export URLs -groups = [basic_urls, rtd_urls, project_urls, api_urls, core_urls, i18n_urls, deprecated_urls] +groups = [ + basic_urls, rtd_urls, project_urls, api_urls, core_urls, i18n_urls, + deprecated_urls +] if settings.DO_NOT_TRACK_ENABLED: # Include Do Not Track URLs if DNT is supported @@ -119,7 +137,7 @@ if settings.USE_PROMOS: if 'readthedocsext.embed' in settings.INSTALLED_APPS: api_urls.insert( 0, - url(r'^api/v1/embed/', include('readthedocsext.embed.urls')) + url(r'^api/v1/embed/', include('readthedocsext.embed.urls')), ) if not getattr(settings, 'USE_SUBDOMAIN', False) or settings.DEBUG: diff --git a/readthedocs/vcs_support/backends/__init__.py b/readthedocs/vcs_support/backends/__init__.py index ff88fa587..37dde39b6 100644 --- a/readthedocs/vcs_support/backends/__init__.py +++ b/readthedocs/vcs_support/backends/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Listing of all the VCS backends.""" from __future__ import absolute_import from . import bzr, hg, git, svn diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py index ebfb5574f..742a8abf3 100644 --- a/readthedocs/vcs_support/backends/bzr.py +++ b/readthedocs/vcs_support/backends/bzr.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Bazaar-related utilities.""" import csv diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index 5d1b4eaa8..cf72f4da2 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Git-related utilities.""" import logging @@ -70,8 +71,7 @@ class Backend(BaseVCS): # TODO remove this after users migrate to a config file from readthedocs.projects.models import Feature submodules_in_config = ( - config.submodules.exclude != ALL or - config.submodules.include + config.submodules.exclude != ALL or config.submodules.include ) if (self.project.has_feature(Feature.SKIP_SUBMODULES) or not submodules_in_config): @@ -103,10 +103,7 @@ class Backend(BaseVCS): Returns the list of invalid submodules. """ repo = git.Repo(self.working_dir) - submodules = { - sub.path: sub - for sub in repo.submodules - } + submodules = {sub.path: sub for sub in repo.submodules} for sub_path in config.submodules.exclude: path = sub_path.rstrip('/') @@ -186,7 +183,7 @@ class Backend(BaseVCS): for tag in repo.tags: try: versions.append(VCSVersion(self, str(tag.commit), str(tag))) - except ValueError as e: + except ValueError: # ValueError: Cannot resolve commit as tag TAGNAME points to a # blob object - use the `.object` property instead to access it # This is not a real tag for us, so we skip it @@ -244,7 +241,7 @@ class Backend(BaseVCS): self.checkout_submodules(submodules, config) else: raise RepositoryError( - RepositoryError.INVALID_SUBMODULES.format(submodules) + RepositoryError.INVALID_SUBMODULES.format(submodules), ) def checkout_submodules(self, submodules, config): diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py index 7bb252322..a21e090ee 100644 --- a/readthedocs/vcs_support/backends/hg.py +++ b/readthedocs/vcs_support/backends/hg.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Mercurial-related utilities.""" from readthedocs.projects.exceptions import RepositoryError from readthedocs.vcs_support.base import BaseVCS, VCSVersion @@ -38,7 +39,11 @@ class Backend(BaseVCS): @property def branches(self): retcode, stdout = self.run( - 'hg', 'branches', '--quiet', record_as_success=True)[:2] + 'hg', + 'branches', + '--quiet', + record_as_success=True, + )[:2] # error (or no tags found) if retcode != 0: return [] diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py index d591dc880..b1a945aec 100644 --- a/readthedocs/vcs_support/backends/svn.py +++ b/readthedocs/vcs_support/backends/svn.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Subversion-related utilities.""" import csv @@ -40,8 +41,13 @@ class Backend(BaseVCS): if retcode != 0: raise RepositoryError retcode, out, err = self.run( - 'svn', 'up', '--accept', 'theirs-full', - '--trust-server-cert', '--non-interactive') + 'svn', + 'up', + '--accept', + 'theirs-full', + '--trust-server-cert', + '--non-interactive', + ) if retcode != 0: raise RepositoryError return retcode, out, err @@ -59,8 +65,12 @@ class Backend(BaseVCS): @property def tags(self): - retcode, stdout = self.run('svn', 'list', '%s/tags/' - % self.base_url, record_as_success=True)[:2] + retcode, stdout = self.run( + 'svn', + 'list', + '%s/tags/' % self.base_url, + record_as_success=True, + )[:2] # error (or no tags found) if retcode != 0: return [] @@ -70,12 +80,12 @@ class Backend(BaseVCS): """ Parses output of svn list, eg: - release-1.1/ - release-1.2/ - release-1.3/ - release-1.4/ - release-1.4.1/ - release-1.5/ + release-1.1/ + release-1.2/ + release-1.3/ + release-1.4/ + release-1.4.1/ + release-1.5/ """ # parse the lines into a list of tuples (commit-hash, tag ref name) # StringIO below is expecting Unicode data, so ensure that it gets it. diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py index 9530cebde..653629110 100644 --- a/readthedocs/vcs_support/base.py +++ b/readthedocs/vcs_support/base.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Base classes for VCS backends.""" import logging import os @@ -26,7 +27,9 @@ class VCSVersion: def __repr__(self): return ' self.timeout: - log.info("Lock (%s): Force unlock, old lockfile", - self.name) + log.info( + 'Lock (%s): Force unlock, old lockfile', + self.name, + ) os.remove(self.fpath) break - log.info("Lock (%s): Locked, waiting..", self.name) + log.info('Lock (%s): Locked, waiting..', self.name) time.sleep(self.polling_interval) timesince = time.time() - start if timesince > self.timeout: - log.info("Lock (%s): Force unlock, timeout reached", - self.name) + log.info( + 'Lock (%s): Force unlock, timeout reached', + self.name, + ) os.remove(self.fpath) break - log.info("%s still locked after %.2f seconds; retry for %.2f" - " seconds", self.name, timesince, self.timeout) + log.info( + '%s still locked after %.2f seconds; retry for %.2f' + ' seconds', + self.name, + timesince, + self.timeout, + ) open(self.fpath, 'w').close() - log.info("Lock (%s): Lock acquired", self.name) + log.info('Lock (%s): Lock acquired', self.name) def __exit__(self, exc, value, tb): try: - log.info("Lock (%s): Releasing", self.name) + log.info('Lock (%s): Releasing', self.name) os.remove(self.fpath) except OSError as e: # We want to ignore "No such file or directory" and log any other # type of error. if e.errno != errno.ENOENT: log.exception( - "Lock (%s): Failed to release, ignoring...", + 'Lock (%s): Failed to release, ignoring...', self.name, ) @@ -81,7 +93,9 @@ class NonBlockingLock: """ def __init__(self, project, version, max_lock_age=None): - self.fpath = os.path.join(project.doc_path, '%s__rtdlock' % version.slug) + self.fpath = os.path.join( + project.doc_path, '%s__rtdlock' % version.slug + ) self.max_lock_age = max_lock_age self.name = project.slug @@ -90,21 +104,23 @@ class NonBlockingLock: if path_exists and self.max_lock_age is not None: lock_age = time.time() - os.stat(self.fpath)[stat.ST_MTIME] if lock_age > self.max_lock_age: - log.info("Lock (%s): Force unlock, old lockfile", - self.name) + log.info( + 'Lock (%s): Force unlock, old lockfile', + self.name, + ) os.remove(self.fpath) else: raise LockTimeout( - "Lock ({}): Lock still active".format(self.name)) + 'Lock ({}): Lock still active'.format(self.name), + ) elif path_exists: - raise LockTimeout( - "Lock ({}): Lock still active".format(self.name)) + raise LockTimeout('Lock ({}): Lock still active'.format(self.name),) open(self.fpath, 'w').close() return self def __exit__(self, exc_type, exc_val, exc_tb): try: - log.info("Lock (%s): Releasing", self.name) + log.info('Lock (%s): Releasing', self.name) os.remove(self.fpath) except (IOError, OSError) as e: # We want to ignore "No such file or directory" and log any other diff --git a/readthedocs/worker.py b/readthedocs/worker.py index 47dfdb482..d038e0062 100644 --- a/readthedocs/worker.py +++ b/readthedocs/worker.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -"""Celery worker application instantiation""" + +"""Celery worker application instantiation.""" import os diff --git a/readthedocs/wsgi.py b/readthedocs/wsgi.py index e1cd6a8ec..9d35da5f3 100644 --- a/readthedocs/wsgi.py +++ b/readthedocs/wsgi.py @@ -1,9 +1,10 @@ -"""WSGI application helper""" +# -*- coding: utf-8 -*- +"""WSGI application helper.""" from __future__ import absolute_import import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "readthedocs.settings.dev") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'readthedocs.settings.dev') # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION From 5b88c0739b66b1a8cc7135e1a89cc4a915753408 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Jan 2019 22:02:58 +0100 Subject: [PATCH 26/65] Add docstring to config.__init__ file --- readthedocs/config/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/readthedocs/config/__init__.py b/readthedocs/config/__init__.py index fd6326a50..d0c70ef6b 100644 --- a/readthedocs/config/__init__.py +++ b/readthedocs/config/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- +"""Logic to parse and validate ``readthedocs.yaml`` file.""" from .config import * # noqa from .parser import * # noqa From 0c535feb2081e3f6a5e1c930de2b4850137f14f8 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Jan 2019 12:51:48 +0100 Subject: [PATCH 27/65] More linting issues fixed --- readthedocs/core/utils/extend.py | 8 ++++++-- readthedocs/core/utils/tasks/public.py | 1 + readthedocs/core/views/__init__.py | 4 ++-- readthedocs/core/views/serve.py | 11 +++++------ readthedocs/doc_builder/backends/sphinx.py | 7 +++++-- readthedocs/doc_builder/environments.py | 5 ++--- .../doc_builder/python_environments.py | 19 ++++++++++++++----- readthedocs/notifications/views.py | 12 +++++------- readthedocs/oauth/notifications.py | 8 ++++---- readthedocs/projects/models.py | 3 ++- readthedocs/projects/querysets.py | 7 ++++--- 11 files changed, 50 insertions(+), 35 deletions(-) diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py index 96319e691..a74b11758 100644 --- a/readthedocs/core/utils/extend.py +++ b/readthedocs/core/utils/extend.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Patterns for extending Read the Docs.""" import inspect @@ -33,8 +34,11 @@ def get_override_class(proxy_class, default_class=None): class SettingsOverrideMeta(type): - """Meta class for passing along classmethod class to the underlying - class.""" # noqa + """ + Meta class to manage our Setting configurations. + + Meta class for passing along classmethod class to the underlying class. + """ def __getattr__(cls, attr): # noqa: pep8 false positive proxy_class = get_override_class(cls, getattr(cls, '_default_class')) diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py index 2f0bb0114..8b8f2421a 100644 --- a/readthedocs/core/utils/tasks/public.py +++ b/readthedocs/core/utils/tasks/public.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Celery tasks with publicly viewable status.""" from celery import Task, states diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 597c2ac62..084b7ca7d 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -133,8 +133,8 @@ def do_not_track(request): dnt_header = request.META.get('HTTP_DNT') # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation - return JsonResponse( - { # pylint: disable=redundant-content-type-for-json-response + return JsonResponse( # pylint: disable=redundant-content-type-for-json-response + { 'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html', 'same-party': [ 'readthedocs.org', diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 9f87d82ab..d996879f8 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -57,9 +57,9 @@ def map_subproject_slug(view_func): """ @wraps(view_func) - def inner_view( + def inner_view( # noqa request, subproject=None, subproject_slug=None, *args, **kwargs - ): # noqa + ): if subproject is None and subproject_slug: # Try to fetch by subproject alias first, otherwise we might end up # redirected to an unrelated project. @@ -87,9 +87,9 @@ def map_project_slug(view_func): """ @wraps(view_func) - def inner_view( + def inner_view( # noqa request, project=None, project_slug=None, *args, **kwargs - ): # noqa + ): if project is None: if not project_slug: project_slug = request.slug @@ -160,8 +160,7 @@ def serve_docs( version_slug=None, filename='', ): - """Exists to map existing proj, lang, version, filename views to the file - format.""" + """Map existing proj, lang, version, filename views to the file format.""" if not version_slug: version_slug = project.get_default_version() try: diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index b656433e5..c5a664835 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -162,8 +162,11 @@ class BaseSphinx(BaseBuilder): return data def append_conf(self, **__): - """Find or create a ``conf.py`` with a rendered - ``doc_builder/conf.py.tmpl`` appended.""" + """ + Find or create a ``conf.py`` and appends default content. + + The default content is rendered from ``doc_builder/conf.py.tmpl``. + """ if self.config_file is None: master_doc = self.create_index(extension='rst') self._write_config(master_doc=master_doc) diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index 9276e187a..497a316a1 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -597,8 +597,7 @@ class BuildEnvironment(BaseEnvironment): @property def successful(self): - """Is build completed, without top level failures or failing - commands.""" # noqa + """Build completed, without top level failures or failing commands.""" return ( self.done and self.failure is None and all(cmd.successful for cmd in self.commands) @@ -667,7 +666,7 @@ class BuildEnvironment(BaseEnvironment): # Surface a generic error if the class is not a # BuildEnvironmentError if not isinstance( - self.failure, + self.failure, ( BuildEnvironmentException, BuildEnvironmentWarning, diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index 76b851880..0f0c6e4e9 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -141,7 +141,7 @@ class PythonEnvironment: environment_conf = json.load(fpath) except (IOError, TypeError, KeyError, ValueError): log.warning( - 'Unable to read/parse readthedocs-environment.json file' + 'Unable to read/parse readthedocs-environment.json file', ) # We remove the JSON file here to avoid cycling over time with a # corrupted file. @@ -176,8 +176,15 @@ class PythonEnvironment: ]) def save_environment_json(self): - """Save on disk Python and build image versions used to create the - venv.""" + """ + Save on builders disk data about the environment used to build docs. + + The data is saved as a ``.json`` file with this information on it: + + - python.version + - build.image + - build.hash + """ data = { 'python': { 'version': self.config.python_full_version, @@ -296,8 +303,10 @@ class Virtualenv(PythonEnvironment): if not requirements_file_path and requirements_file_path != '': builder_class = get_builder_class(self.config.doctype) docs_dir = ( - builder_class(build_env=self.build_env, - python_env=self).docs_dir() + builder_class( + build_env=self.build_env, + python_env=self, + ).docs_dir() ) paths = [docs_dir, ''] req_files = ['pip_requirements.txt', 'requirements.txt'] diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py index 7252e8c3e..33e6da582 100644 --- a/readthedocs/notifications/views.py +++ b/readthedocs/notifications/views.py @@ -14,13 +14,11 @@ class SendNotificationView(FormView): Accepts the following additional parameters: - queryset - The queryset to use to determine the users to send emails to - - :cvar action_name: Name of the action to pass to the form template, - determines the action to pass back to the admin view - :cvar notification_classes: List of :py:class:`Notification` classes to - display in the form + :param queryset: Queryset to use to determine the users to send emails to + :param action_name: Name of the action to pass to the form template, + determines the action to pass back to the admin view + :param notification_classes: List of :py:class:`Notification` classes to + display in the form """ form_class = SendNotificationForm diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py index afc9e43c8..062dfe741 100644 --- a/readthedocs/oauth/notifications.py +++ b/readthedocs/oauth/notifications.py @@ -16,11 +16,11 @@ class AttachWebhookNotification(SiteNotification): success_message = _('Webhook successfully added.') failure_message = { NO_PERMISSIONS: _( - 'Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.' - ), # noqa + 'Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.' # noqa + ), NO_ACCOUNTS: _( - 'Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.' - ), # noqa + 'Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.' # noqa + ), } def get_context_data(self): diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index cc34197bf..d552c2b28 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1045,7 +1045,8 @@ class APIProject(Project): 'absolute_url', 'downloads', 'main_language_project', - 'related_projects',]: + 'related_projects', + ]: try: del kwargs[key] except KeyError: diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py index 396003dad..cddcf30ef 100644 --- a/readthedocs/projects/querysets.py +++ b/readthedocs/projects/querysets.py @@ -92,10 +92,11 @@ class ProjectQuerySet(SettingsOverrideObject): class RelatedProjectQuerySetBase(models.QuerySet): """ - A manager for things that relate to Project and need to get their perms from - the project. + Useful for objects that relate to Project and its permissions. - This shouldn't be used as a subclass. + Objects get the permissions from the project itself. + + ..note:: This shouldn't be used as a subclass. """ use_for_related_fields = True From f1820596f0c3c175d5c5bc147b80652ed788d5f6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Jan 2019 12:53:25 +0100 Subject: [PATCH 28/65] pre-commit run on some missing files (manually applyed) --- readthedocs/doc_builder/environments.py | 10 +++--- .../doc_builder/python_environments.py | 6 ++-- readthedocs/notifications/views.py | 4 ++- readthedocs/oauth/notifications.py | 4 +-- readthedocs/projects/models.py | 31 ++++++++++++------- readthedocs/projects/querysets.py | 5 +-- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index 497a316a1..ed158933b 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -312,14 +312,14 @@ class DockerBuildCommand(BuildCommand): # not use the specific exit code, so we check if the word `Killed` # is in the last 15 lines of the command's output killed_in_output = 'Killed' in '\n'.join( - self.output.splitlines()[-15:] + self.output.splitlines()[-15:], ) if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and killed_in_output): self.output += str( _( '\n\nCommand killed due to excessive memory consumption\n', - ) + ), ) except DockerAPIError: self.exit_code = -1 @@ -444,7 +444,7 @@ class BaseEnvironment: project=self.project.slug, version='latest', msg=msg, - ) + ), ) else: raise BuildEnvironmentWarning(msg) @@ -792,7 +792,7 @@ class DockerBuildEnvironment(BuildEnvironment): version=self.version.slug, msg=( 'Removing stale container {}'.format( - self.container_id + self.container_id, ) ), ), @@ -966,7 +966,7 @@ class DockerBuildEnvironment(BuildEnvironment): elif state.get('Error'): self.failure = BuildEnvironmentError(( _('Build exited due to unknown error: {0}').format( - state.get('Error') + state.get('Error'), ) ),) diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index 0f0c6e4e9..887395861 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -49,7 +49,7 @@ class PythonEnvironment: project=self.project.slug, version=self.version.slug, msg='Removing existing build directory', - ) + ), ) shutil.rmtree(build_dir) @@ -62,7 +62,7 @@ class PythonEnvironment: project=self.project.slug, version=self.version.slug, msg='Removing existing venv directory', - ) + ), ) shutil.rmtree(venv_dir) @@ -360,7 +360,7 @@ class Conda(PythonEnvironment): project=self.project.slug, version=self.version.slug, msg='Removing existing conda directory', - ) + ), ) shutil.rmtree(version_path) self.build_env.run( diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py index 33e6da582..0ee0495dd 100644 --- a/readthedocs/notifications/views.py +++ b/readthedocs/notifications/views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django views for the notifications app.""" from django.contrib import admin, messages from django.http import HttpResponseRedirect @@ -106,7 +107,8 @@ class SendNotificationView(FormView): fail_silently=False, ): """ - Implementation of + Implementation of. + :py:meth:`django.contrib.admin.options.ModelAdmin.message_user` Send message through messages framework diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py index 062dfe741..c5c5165aa 100644 --- a/readthedocs/oauth/notifications.py +++ b/readthedocs/oauth/notifications.py @@ -16,10 +16,10 @@ class AttachWebhookNotification(SiteNotification): success_message = _('Webhook successfully added.') failure_message = { NO_PERMISSIONS: _( - 'Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.' # noqa + 'Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.', # noqa ), NO_ACCOUNTS: _( - 'Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.' # noqa + 'Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.', # noqa ), } diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index d552c2b28..12cf77ea9 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -62,7 +62,11 @@ class ProjectRelationship(models.Model): related_name='superprojects', ) alias = models.SlugField( - _('Alias'), max_length=255, null=True, blank=True, db_index=False + _('Alias'), + max_length=255, + null=True, + blank=True, + db_index=False, ) objects = ChildRelatedProjectQuerySet.as_manager() @@ -476,8 +480,10 @@ class Project(models.Model): Always use http for now, to avoid content warnings. """ return resolve( - project=self, version_slug=version_slug, language=lang_slug, - private=private + project=self, + version_slug=version_slug, + language=lang_slug, + private=private, ) def get_builds_url(self): @@ -730,7 +736,7 @@ class Project(models.Model): self.get_production_media_path( type_='pdf', version_slug=version_slug, - ) + ), ) def has_epub(self, version_slug=LATEST): @@ -740,7 +746,7 @@ class Project(models.Model): self.get_production_media_path( type_='epub', version_slug=version_slug, - ) + ), ) def has_htmlzip(self, version_slug=LATEST): @@ -748,7 +754,7 @@ class Project(models.Model): self.get_production_media_path( type_='htmlzip', version_slug=version_slug, - ) + ), ) @property @@ -841,8 +847,7 @@ class Project(models.Model): def api_versions(self): from readthedocs.builds.models import APIVersion ret = [] - for version_data in api.project(self.pk - ).active_versions.get()['versions']: + for version_data in api.project(self.pk).active_versions.get()['versions']: version = APIVersion(**version_data) ret.append(version) return sort_version_aware(ret) @@ -1103,8 +1108,9 @@ class ImportedFile(models.Model): def get_absolute_url(self): return resolve( - project=self.project, version_slug=self.version.slug, - filename=self.path + project=self.project, + version_slug=self.version.slug, + filename=self.path, ) def __str__(self): @@ -1188,7 +1194,8 @@ class Domain(models.Model): def __str__(self): return '{domain} pointed at {project}'.format( - domain=self.domain, project=self.project.name + domain=self.domain, + project=self.project.name, ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -1264,7 +1271,7 @@ class Feature(models.Model): ), ( MKDOCS_THEME_RTD, - _('Use Read the Docs theme for MkDocs as default theme') + _('Use Read the Docs theme for MkDocs as default theme'), ), ( DONT_SHALLOW_CLONE, diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py index cddcf30ef..dcbf1ffda 100644 --- a/readthedocs/projects/querysets.py +++ b/readthedocs/projects/querysets.py @@ -45,7 +45,7 @@ class ProjectQuerySetBase(models.QuerySet): def protected(self, user=None): queryset = self.filter( - privacy_level__in=[constants.PUBLIC, constants.PROTECTED] + privacy_level__in=[constants.PUBLIC, constants.PROTECTED], ) if user: return self._add_user_repos(queryset, user) @@ -126,7 +126,8 @@ class RelatedProjectQuerySetBase(models.QuerySet): def protected(self, user=None, project=None): kwargs = { '%s__privacy_level__in' % self.project_field: [ - constants.PUBLIC, constants.PROTECTED + constants.PUBLIC, + constants.PROTECTED, ], } queryset = self.filter(**kwargs) From 47e5e7e753f7d6650242a9fb9e94363a39d1f506 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Jan 2019 12:53:53 +0100 Subject: [PATCH 29/65] Update common pre-commit configs --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 890625b9b..bb9f74e7d 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 890625b9bfca1981a3f68a9fb7d168bba714421d +Subproject commit bb9f74e7db578fb575bb3a1f48a4ee6c3bc2a491 From efa731ff4f50a5e2102291518384b094920707e8 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Jan 2019 13:08:20 +0100 Subject: [PATCH 30/65] Upgdate common submodule --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index bb9f74e7d..8712295c9 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit bb9f74e7db578fb575bb3a1f48a4ee6c3bc2a491 +Subproject commit 8712295c9d5acf4570350f22849cb705f522ea60 From 632e18c1212981a69aea243f5d9349498501f999 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Jan 2019 13:19:00 +0100 Subject: [PATCH 31/65] pre-commit all the files again --- readthedocs/analytics/__init__.py | 1 + readthedocs/analytics/apps.py | 1 + readthedocs/analytics/tasks.py | 1 + readthedocs/analytics/utils.py | 1 + readthedocs/api/utils.py | 1 + readthedocs/builds/admin.py | 1 + readthedocs/builds/constants.py | 1 + readthedocs/builds/forms.py | 1 + readthedocs/builds/managers.py | 1 + readthedocs/builds/querysets.py | 1 + readthedocs/builds/signals.py | 1 + readthedocs/builds/syncers.py | 1 + readthedocs/builds/version_slug.py | 1 + readthedocs/config/__init__.py | 1 + readthedocs/config/find.py | 1 + readthedocs/config/models.py | 1 + readthedocs/config/validation.py | 1 + readthedocs/constants.py | 1 + readthedocs/core/__init__.py | 1 + readthedocs/core/adapters.py | 1 + readthedocs/core/admin.py | 1 + readthedocs/core/apps.py | 1 + readthedocs/core/backends.py | 1 + readthedocs/core/context_processors.py | 1 + readthedocs/core/fields.py | 1 + readthedocs/core/management/commands/archive.py | 1 + .../core/management/commands/clean_builds.py | 1 + .../core/management/commands/import_github.py | 1 + .../commands/import_github_language.py | 1 + .../commands/provision_elasticsearch.py | 1 + readthedocs/core/management/commands/pull.py | 1 + .../management/commands/reindex_elasticsearch.py | 1 + .../core/management/commands/set_metadata.py | 1 + readthedocs/core/management/commands/symlink.py | 1 + .../core/management/commands/update_api.py | 1 + .../core/management/commands/update_versions.py | 1 + readthedocs/core/middleware.py | 1 + readthedocs/core/mixins.py | 1 + readthedocs/core/permissions.py | 1 + readthedocs/core/resolver.py | 1 + readthedocs/core/symlink.py | 6 ++++-- readthedocs/core/tasks.py | 1 + readthedocs/core/templatetags/core_tags.py | 1 + readthedocs/core/templatetags/privacy_tags.py | 1 + readthedocs/core/urls/__init__.py | 1 + readthedocs/core/urls/single_version.py | 1 + readthedocs/core/urls/subdomain.py | 1 + readthedocs/core/utils/tasks/__init__.py | 1 + .../core/utils/tasks/permission_checks.py | 1 + readthedocs/core/utils/tasks/retrieve.py | 1 + readthedocs/core/views/hooks.py | 1 + readthedocs/doc_builder/backends/mkdocs.py | 1 + readthedocs/doc_builder/environments.py | 2 ++ readthedocs/doc_builder/loader.py | 1 + readthedocs/doc_builder/signals.py | 1 + readthedocs/gold/__init__.py | 1 + readthedocs/gold/admin.py | 1 + readthedocs/gold/apps.py | 1 + readthedocs/gold/forms.py | 1 + readthedocs/gold/signals.py | 1 + readthedocs/gold/urls.py | 1 + readthedocs/integrations/admin.py | 1 + readthedocs/integrations/utils.py | 1 + readthedocs/notifications/__init__.py | 1 + readthedocs/notifications/apps.py | 1 + readthedocs/notifications/constants.py | 1 + readthedocs/notifications/forms.py | 1 + readthedocs/notifications/urls.py | 1 + readthedocs/oauth/admin.py | 1 + readthedocs/oauth/apps.py | 1 + readthedocs/oauth/querysets.py | 1 + readthedocs/oauth/services/bitbucket.py | 1 + readthedocs/oauth/services/github.py | 1 + readthedocs/payments/forms.py | 1 + readthedocs/payments/mixins.py | 1 + readthedocs/payments/utils.py | 1 + readthedocs/profiles/urls/private.py | 1 + readthedocs/profiles/urls/public.py | 1 + readthedocs/projects/apps.py | 1 + readthedocs/projects/backends/views.py | 1 + readthedocs/projects/feeds.py | 1 + .../commands/import_project_from_live.py | 1 + readthedocs/projects/models.py | 16 +++++++++------- readthedocs/projects/notifications.py | 1 + .../projects/templatetags/projects_tags.py | 1 + readthedocs/projects/urls/private.py | 1 + readthedocs/projects/urls/public.py | 1 + readthedocs/projects/validators.py | 1 + readthedocs/projects/views/mixins.py | 1 + readthedocs/redirects/admin.py | 1 + readthedocs/redirects/managers.py | 1 + readthedocs/redirects/models.py | 1 + readthedocs/redirects/utils.py | 1 + readthedocs/restapi/permissions.py | 1 + readthedocs/restapi/serializers.py | 1 + readthedocs/restapi/signals.py | 1 + readthedocs/restapi/views/core_views.py | 1 + readthedocs/restapi/views/search_views.py | 1 + readthedocs/restapi/views/task_views.py | 1 + readthedocs/search/indexes.py | 1 + readthedocs/search/lib.py | 1 + readthedocs/search/signals.py | 1 + readthedocs/vcs_support/backends/__init__.py | 1 + readthedocs/vcs_support/utils.py | 1 + readthedocs/wsgi.py | 1 + 105 files changed, 117 insertions(+), 9 deletions(-) diff --git a/readthedocs/analytics/__init__.py b/readthedocs/analytics/__init__.py index b8f7ef290..b25cef940 100644 --- a/readthedocs/analytics/__init__.py +++ b/readthedocs/analytics/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """App init.""" default_app_config = 'readthedocs.analytics.apps.AnalyticsAppConfig' # noqa diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py index 3bfa8a355..c2abb221a 100644 --- a/readthedocs/analytics/apps.py +++ b/readthedocs/analytics/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django app config for the analytics app.""" from django.apps import AppConfig diff --git a/readthedocs/analytics/tasks.py b/readthedocs/analytics/tasks.py index 02b0a7524..4e2cc957e 100644 --- a/readthedocs/analytics/tasks.py +++ b/readthedocs/analytics/tasks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Tasks for Read the Docs' analytics.""" from django.conf import settings diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py index 8c06e6001..c358423e4 100644 --- a/readthedocs/analytics/utils.py +++ b/readthedocs/analytics/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utilities related to analytics.""" import hashlib diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py index 1f112a608..ab820d21d 100644 --- a/readthedocs/api/utils.py +++ b/readthedocs/api/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utility classes for api module.""" import logging diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py index 36b64339d..b4bc390b9 100644 --- a/readthedocs/builds/admin.py +++ b/readthedocs/builds/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django admin interface for `~builds.models.Build` and related models.""" from django.contrib import admin diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py index 1175874cf..94858300c 100644 --- a/readthedocs/builds/constants.py +++ b/readthedocs/builds/constants.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Constants for the builds app.""" from django.conf import settings diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index 13f72a5b9..406da7417 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django forms for the builds app.""" from django import forms diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py index d05308cc4..be9c7050e 100644 --- a/readthedocs/builds/managers.py +++ b/readthedocs/builds/managers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Build and Version class model Managers.""" from django.db import models diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py index 5c3778257..2039aa0ec 100644 --- a/readthedocs/builds/querysets.py +++ b/readthedocs/builds/querysets.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Build and Version QuerySet classes.""" from django.db import models diff --git a/readthedocs/builds/signals.py b/readthedocs/builds/signals.py index fb16ca75d..df87930f9 100644 --- a/readthedocs/builds/signals.py +++ b/readthedocs/builds/signals.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Build signals.""" import django.dispatch diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py index 185e0d309..6b7b1d337 100644 --- a/readthedocs/builds/syncers.py +++ b/readthedocs/builds/syncers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Classes to copy files between build and web servers. diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py index d3e8bbe14..b7f88cd58 100644 --- a/readthedocs/builds/version_slug.py +++ b/readthedocs/builds/version_slug.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Contains logic for handling version slugs. diff --git a/readthedocs/config/__init__.py b/readthedocs/config/__init__.py index d0c70ef6b..23006b4c4 100644 --- a/readthedocs/config/__init__.py +++ b/readthedocs/config/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Logic to parse and validate ``readthedocs.yaml`` file.""" from .config import * # noqa from .parser import * # noqa diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py index 6fc3cb6a7..2fe5292c4 100644 --- a/readthedocs/config/find.py +++ b/readthedocs/config/find.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Helper functions to search files.""" import os diff --git a/readthedocs/config/models.py b/readthedocs/config/models.py index 1153fe3f9..bc6f381e8 100644 --- a/readthedocs/config/models.py +++ b/readthedocs/config/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Models for the response of the configuration object.""" from collections import namedtuple diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py index b1840b880..5d7651dff 100644 --- a/readthedocs/config/validation.py +++ b/readthedocs/config/validation.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Validations for the RTD configuration file.""" import os diff --git a/readthedocs/constants.py b/readthedocs/constants.py index 485b93739..579b93771 100644 --- a/readthedocs/constants.py +++ b/readthedocs/constants.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Common constants.""" from readthedocs.builds.version_slug import VERSION_SLUG_REGEX diff --git a/readthedocs/core/__init__.py b/readthedocs/core/__init__.py index c6205d82c..21fae5057 100644 --- a/readthedocs/core/__init__.py +++ b/readthedocs/core/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """App initialization.""" default_app_config = 'readthedocs.core.apps.CoreAppConfig' diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py index aa2fad046..c91ad9c36 100644 --- a/readthedocs/core/adapters.py +++ b/readthedocs/core/adapters.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Allauth overrides.""" import json diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py index da3ebfa15..d0f2051d4 100644 --- a/readthedocs/core/admin.py +++ b/readthedocs/core/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django admin interface for core models.""" from datetime import timedelta diff --git a/readthedocs/core/apps.py b/readthedocs/core/apps.py index 0944efc16..ac5a39d63 100644 --- a/readthedocs/core/apps.py +++ b/readthedocs/core/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """App configurations for core app.""" from django.apps import AppConfig diff --git a/readthedocs/core/backends.py b/readthedocs/core/backends.py index 381be54dc..b7f9bf788 100644 --- a/readthedocs/core/backends.py +++ b/readthedocs/core/backends.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Email backends for core app.""" import smtplib diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py index 815afbdaa..536c1fe46 100644 --- a/readthedocs/core/context_processors.py +++ b/readthedocs/core/context_processors.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Template context processors for core app.""" from django.conf import settings diff --git a/readthedocs/core/fields.py b/readthedocs/core/fields.py index 1b6d9f994..5801d3014 100644 --- a/readthedocs/core/fields.py +++ b/readthedocs/core/fields.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Shared model fields and defaults.""" import binascii diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py index e90d8de00..14e87f7df 100644 --- a/readthedocs/core/management/commands/archive.py +++ b/readthedocs/core/management/commands/archive.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Rebuild documentation for all projects.""" import logging diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py index 64d52ec84..e6f032824 100644 --- a/readthedocs/core/management/commands/clean_builds.py +++ b/readthedocs/core/management/commands/clean_builds.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Clean up stable build paths per project version.""" import logging diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py index 85530968f..6c8cb5b5c 100644 --- a/readthedocs/core/management/commands/import_github.py +++ b/readthedocs/core/management/commands/import_github.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Resync GitHub project for user.""" from django.contrib.auth.models import User diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index e3679a526..f0da49cdd 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Import a project's programming language from GitHub. diff --git a/readthedocs/core/management/commands/provision_elasticsearch.py b/readthedocs/core/management/commands/provision_elasticsearch.py index adab81d49..b1efe643c 100644 --- a/readthedocs/core/management/commands/provision_elasticsearch.py +++ b/readthedocs/core/management/commands/provision_elasticsearch.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Provision Elastic Search.""" import logging diff --git a/readthedocs/core/management/commands/pull.py b/readthedocs/core/management/commands/pull.py index 3c965f8b1..ae62f9da1 100644 --- a/readthedocs/core/management/commands/pull.py +++ b/readthedocs/core/management/commands/pull.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Trigger build for project slug.""" import logging diff --git a/readthedocs/core/management/commands/reindex_elasticsearch.py b/readthedocs/core/management/commands/reindex_elasticsearch.py index 0d552e86e..bcc199a26 100644 --- a/readthedocs/core/management/commands/reindex_elasticsearch.py +++ b/readthedocs/core/management/commands/reindex_elasticsearch.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Reindex Elastic Search indexes.""" import logging diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py index e5782e404..33736b7a7 100644 --- a/readthedocs/core/management/commands/set_metadata.py +++ b/readthedocs/core/management/commands/set_metadata.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Generate metadata for all projects.""" import logging diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py index a7743a740..41719354c 100644 --- a/readthedocs/core/management/commands/symlink.py +++ b/readthedocs/core/management/commands/symlink.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Update symlinks for projects.""" import logging diff --git a/readthedocs/core/management/commands/update_api.py b/readthedocs/core/management/commands/update_api.py index 5b87bd4a5..68b695c4d 100644 --- a/readthedocs/core/management/commands/update_api.py +++ b/readthedocs/core/management/commands/update_api.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Build documentation using the API and not hitting a database. diff --git a/readthedocs/core/management/commands/update_versions.py b/readthedocs/core/management/commands/update_versions.py index bf066c775..aacb47635 100644 --- a/readthedocs/core/management/commands/update_versions.py +++ b/readthedocs/core/management/commands/update_versions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Rebuild documentation for all projects.""" from django.core.management.base import BaseCommand diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py index 1a874e120..c40dbfd0f 100644 --- a/readthedocs/core/middleware.py +++ b/readthedocs/core/middleware.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Middleware for core app.""" import logging diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py index a2cf841ae..4d6b1160c 100644 --- a/readthedocs/core/mixins.py +++ b/readthedocs/core/mixins.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Common mixin classes for views.""" from django.contrib.auth.decorators import login_required diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py index 28d01bd7a..1c397eae1 100644 --- a/readthedocs/core/permissions.py +++ b/readthedocs/core/permissions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Objects for User permission checks.""" from readthedocs.core.utils.extend import SettingsOverrideObject diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index 10b5f42d5..389fedc98 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL resolver for documentation.""" import re diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index b43da937e..3de4c2b37 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ A class that manages the symlinks for nginx to serve public files. @@ -297,8 +298,9 @@ class Symlink: """ Symlink project single version. - Link from $WEB_ROOT/ -> - HOME/user_builds//rtd-builds/latest/ + Link from: + + $WEB_ROOT/ -> HOME/user_builds//rtd-builds/latest/ """ version = self.get_default_version() diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py index d053614c5..e4f3e329c 100644 --- a/readthedocs/core/tasks.py +++ b/readthedocs/core/tasks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Basic tasks.""" import logging diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index b4862e525..3df7c5769 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Template tags for core app.""" import hashlib diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index fbbe98041..e81a6f3a3 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Template tags to query projects by privacy.""" from django import template diff --git a/readthedocs/core/urls/__init__.py b/readthedocs/core/urls/__init__.py index 4c0951e9c..60e7fd323 100644 --- a/readthedocs/core/urls/__init__.py +++ b/readthedocs/core/urls/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL configuration for core app.""" from __future__ import absolute_import diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py index f8d33a761..dedc458e9 100644 --- a/readthedocs/core/urls/single_version.py +++ b/readthedocs/core/urls/single_version.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL configuration for a single version.""" from functools import reduce from operator import add diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index cddc49a98..b639bc473 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL configurations for subdomains.""" from functools import reduce from operator import add diff --git a/readthedocs/core/utils/tasks/__init__.py b/readthedocs/core/utils/tasks/__init__.py index 492708ad5..7aede3ac1 100644 --- a/readthedocs/core/utils/tasks/__init__.py +++ b/readthedocs/core/utils/tasks/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Common task exports.""" from .permission_checks import user_id_matches # noqa for unused import diff --git a/readthedocs/core/utils/tasks/permission_checks.py b/readthedocs/core/utils/tasks/permission_checks.py index 4a32e6a1a..84f545830 100644 --- a/readthedocs/core/utils/tasks/permission_checks.py +++ b/readthedocs/core/utils/tasks/permission_checks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Permission checks for tasks.""" __all__ = ('user_id_matches',) diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py index a2b008d32..9281ad8a1 100644 --- a/readthedocs/core/utils/tasks/retrieve.py +++ b/readthedocs/core/utils/tasks/retrieve.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utilities for retrieving task data.""" from celery import states diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 16213e24b..b97583c64 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Views pertaining to builds.""" import json diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 805eb0ce5..5f96a25a6 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ MkDocs_ backend for building docs. diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index ed158933b..f1517cf7c 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -665,6 +665,7 @@ class BuildEnvironment(BaseEnvironment): if self.failure is not None: # Surface a generic error if the class is not a # BuildEnvironmentError + # yapf: disable if not isinstance( self.failure, ( @@ -672,6 +673,7 @@ class BuildEnvironment(BaseEnvironment): BuildEnvironmentWarning, ), ): + # yapf: enable log.error( 'Build failed with unhandled exception: %s', str(self.failure), diff --git a/readthedocs/doc_builder/loader.py b/readthedocs/doc_builder/loader.py index b878009d2..016cc6bba 100644 --- a/readthedocs/doc_builder/loader.py +++ b/readthedocs/doc_builder/loader.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Lookup tables for builders and backends.""" from importlib import import_module diff --git a/readthedocs/doc_builder/signals.py b/readthedocs/doc_builder/signals.py index 9911ba3ab..5821ddecc 100644 --- a/readthedocs/doc_builder/signals.py +++ b/readthedocs/doc_builder/signals.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Signals for adding custom context data.""" import django.dispatch diff --git a/readthedocs/gold/__init__.py b/readthedocs/gold/__init__.py index b7fcb9ea1..2ef7166ad 100644 --- a/readthedocs/gold/__init__.py +++ b/readthedocs/gold/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ A Django app for Gold Membership. diff --git a/readthedocs/gold/admin.py b/readthedocs/gold/admin.py index 020d2be94..2da5d0334 100644 --- a/readthedocs/gold/admin.py +++ b/readthedocs/gold/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django admin configuration for the Gold Membership app.""" from django.contrib import admin diff --git a/readthedocs/gold/apps.py b/readthedocs/gold/apps.py index 5891264c8..c54b9d542 100644 --- a/readthedocs/gold/apps.py +++ b/readthedocs/gold/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django app configuration for the Gold Membership app.""" from django.apps import AppConfig diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index 4840745d8..730f29296 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Gold subscription forms.""" from django import forms diff --git a/readthedocs/gold/signals.py b/readthedocs/gold/signals.py index 155230f38..a5b377290 100644 --- a/readthedocs/gold/signals.py +++ b/readthedocs/gold/signals.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Gold model signals.""" from django.db.models.signals import pre_delete diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py index c57ccab39..2a6b2047b 100644 --- a/readthedocs/gold/urls.py +++ b/readthedocs/gold/urls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Gold subscription URLs.""" from django.conf.urls import url diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py index 06001c024..9b3286473 100644 --- a/readthedocs/integrations/admin.py +++ b/readthedocs/integrations/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Integration admin models.""" from django import urls diff --git a/readthedocs/integrations/utils.py b/readthedocs/integrations/utils.py index e4c78bf47..0939a36a8 100644 --- a/readthedocs/integrations/utils.py +++ b/readthedocs/integrations/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Integration utility functions.""" diff --git a/readthedocs/notifications/__init__.py b/readthedocs/notifications/__init__.py index 74496a1c9..2802d286a 100644 --- a/readthedocs/notifications/__init__.py +++ b/readthedocs/notifications/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Extensions to Django messages to support notifications to users. diff --git a/readthedocs/notifications/apps.py b/readthedocs/notifications/apps.py index 9876fdb52..60543374e 100644 --- a/readthedocs/notifications/apps.py +++ b/readthedocs/notifications/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django app configuration for the notifications app.""" from django.apps import AppConfig diff --git a/readthedocs/notifications/constants.py b/readthedocs/notifications/constants.py index 2716e6dfb..640170aff 100644 --- a/readthedocs/notifications/constants.py +++ b/readthedocs/notifications/constants.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Notification constants.""" from messages_extends import constants as message_constants diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py index 4a6d48039..83b68b2cf 100644 --- a/readthedocs/notifications/forms.py +++ b/readthedocs/notifications/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """HTML forms for sending notifications.""" from django import forms from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/notifications/urls.py b/readthedocs/notifications/urls.py index 1b03e5b38..91803e3ef 100644 --- a/readthedocs/notifications/urls.py +++ b/readthedocs/notifications/urls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Renames for messages_extends URLs.""" from django.conf.urls import url diff --git a/readthedocs/oauth/admin.py b/readthedocs/oauth/admin.py index dd221e9b2..eeee459e6 100644 --- a/readthedocs/oauth/admin.py +++ b/readthedocs/oauth/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Admin configuration for the OAuth app.""" from django.contrib import admin diff --git a/readthedocs/oauth/apps.py b/readthedocs/oauth/apps.py index c064f8fc8..148687390 100644 --- a/readthedocs/oauth/apps.py +++ b/readthedocs/oauth/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """OAuth app config.""" from django.apps import AppConfig diff --git a/readthedocs/oauth/querysets.py b/readthedocs/oauth/querysets.py index f410e9666..e7f20dc18 100644 --- a/readthedocs/oauth/querysets.py +++ b/readthedocs/oauth/querysets.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Managers for OAuth models.""" from django.db import models diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py index b980df55d..e267e5afe 100644 --- a/readthedocs/oauth/services/bitbucket.py +++ b/readthedocs/oauth/services/bitbucket.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """OAuth utility functions.""" import json diff --git a/readthedocs/oauth/services/github.py b/readthedocs/oauth/services/github.py index 19bd3c376..a3f22b0e1 100644 --- a/readthedocs/oauth/services/github.py +++ b/readthedocs/oauth/services/github.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """OAuth utility functions.""" import json diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py index e64813e6a..d313c9e55 100644 --- a/readthedocs/payments/forms.py +++ b/readthedocs/payments/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Payment forms.""" import logging diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py index 2ba34ec47..4bce56d82 100644 --- a/readthedocs/payments/mixins.py +++ b/readthedocs/payments/mixins.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Payment view mixin classes.""" from django.conf import settings diff --git a/readthedocs/payments/utils.py b/readthedocs/payments/utils.py index e85159ae9..ba1b045a2 100644 --- a/readthedocs/payments/utils.py +++ b/readthedocs/payments/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Payment utility functions. diff --git a/readthedocs/profiles/urls/private.py b/readthedocs/profiles/urls/private.py index 1fe6d2886..ca6c13428 100644 --- a/readthedocs/profiles/urls/private.py +++ b/readthedocs/profiles/urls/private.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL patterns for views to modify user profiles.""" from django.conf.urls import url diff --git a/readthedocs/profiles/urls/public.py b/readthedocs/profiles/urls/public.py index 7b0284312..d2cec291b 100644 --- a/readthedocs/profiles/urls/public.py +++ b/readthedocs/profiles/urls/public.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """URL patterns to view user profiles.""" from django.conf.urls import url diff --git a/readthedocs/projects/apps.py b/readthedocs/projects/apps.py index 818c5b5e3..76b3fae1b 100644 --- a/readthedocs/projects/apps.py +++ b/readthedocs/projects/apps.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project app config.""" from django.apps import AppConfig diff --git a/readthedocs/projects/backends/views.py b/readthedocs/projects/backends/views.py index 326a7c963..ebae2d8b1 100644 --- a/readthedocs/projects/backends/views.py +++ b/readthedocs/projects/backends/views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Project views loaded by configuration settings. diff --git a/readthedocs/projects/feeds.py b/readthedocs/projects/feeds.py index 4a578c6a9..35ebdd6ac 100644 --- a/readthedocs/projects/feeds.py +++ b/readthedocs/projects/feeds.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project RSS feeds.""" from django.contrib.syndication.views import Feed diff --git a/readthedocs/projects/management/commands/import_project_from_live.py b/readthedocs/projects/management/commands/import_project_from_live.py index 4f823cffd..2b006f84e 100644 --- a/readthedocs/projects/management/commands/import_project_from_live.py +++ b/readthedocs/projects/management/commands/import_project_from_live.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Import project command.""" import json diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 12cf77ea9..1a901204d 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -847,7 +847,7 @@ class Project(models.Model): def api_versions(self): from readthedocs.builds.models import APIVersion ret = [] - for version_data in api.project(self.pk).active_versions.get()['versions']: + for version_data in api.project(self.pk).active_versions.get()['versions']: # yapf: disable version = APIVersion(**version_data) ret.append(version) return sort_version_aware(ret) @@ -1044,14 +1044,16 @@ class APIProject(Project): ad_free = (not kwargs.pop('show_advertising', True)) # These fields only exist on the API return, not on the model, so we'll # remove them to avoid throwing exceptions due to unexpected fields + # yapf: disable for key in [ - 'users', - 'resource_uri', - 'absolute_url', - 'downloads', - 'main_language_project', - 'related_projects', + 'users', + 'resource_uri', + 'absolute_url', + 'downloads', + 'main_language_project', + 'related_projects', ]: + # yapf: enable try: del kwargs[key] except KeyError: diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 9d108ab69..2dc108409 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project notifications.""" from readthedocs.notifications import Notification diff --git a/readthedocs/projects/templatetags/projects_tags.py b/readthedocs/projects/templatetags/projects_tags.py index a655487a0..6a6d6d16a 100644 --- a/readthedocs/projects/templatetags/projects_tags.py +++ b/readthedocs/projects/templatetags/projects_tags.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project template tags and filters.""" from django import template diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index 82c29500c..62449ca1f 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project URLs for authenticated users.""" from django.conf.urls import url diff --git a/readthedocs/projects/urls/public.py b/readthedocs/projects/urls/public.py index 7b17b476d..f353714ac 100644 --- a/readthedocs/projects/urls/public.py +++ b/readthedocs/projects/urls/public.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project URLS for public users.""" from django.conf.urls import url diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py index 8fe3bf7f5..dc64b3fc0 100644 --- a/readthedocs/projects/validators.py +++ b/readthedocs/projects/validators.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Validators for projects app.""" import re diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py index 5ce119e4e..670caa21f 100644 --- a/readthedocs/projects/views/mixins.py +++ b/readthedocs/projects/views/mixins.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Mixin classes for project views.""" from django.shortcuts import get_object_or_404 diff --git a/readthedocs/redirects/admin.py b/readthedocs/redirects/admin.py index 8b9782265..4ce0239d4 100644 --- a/readthedocs/redirects/admin.py +++ b/readthedocs/redirects/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django admin configuration for the redirects app.""" from django.contrib import admin diff --git a/readthedocs/redirects/managers.py b/readthedocs/redirects/managers.py index c0f48f83e..37e10890c 100644 --- a/readthedocs/redirects/managers.py +++ b/readthedocs/redirects/managers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Manager and queryset for the redirects app.""" from django.db.models import Manager diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py index 478b74630..7945ecb5a 100644 --- a/readthedocs/redirects/models.py +++ b/readthedocs/redirects/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django models for the redirects app.""" import logging diff --git a/readthedocs/redirects/utils.py b/readthedocs/redirects/utils.py index e6446e136..ce1d75140 100644 --- a/readthedocs/redirects/utils.py +++ b/readthedocs/redirects/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Redirection view support. diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py index e08087824..93d4695a7 100644 --- a/readthedocs/restapi/permissions.py +++ b/readthedocs/restapi/permissions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Defines access permissions for the API.""" from rest_framework import permissions diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py index bded753b4..17778f777 100644 --- a/readthedocs/restapi/serializers.py +++ b/readthedocs/restapi/serializers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Defines serializers for each of our models.""" from allauth.socialaccount.models import SocialAccount diff --git a/readthedocs/restapi/signals.py b/readthedocs/restapi/signals.py index e43a70134..65509fc55 100644 --- a/readthedocs/restapi/signals.py +++ b/readthedocs/restapi/signals.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """We define custom Django signals to trigger when a footer is rendered.""" import django.dispatch diff --git a/readthedocs/restapi/views/core_views.py b/readthedocs/restapi/views/core_views.py index 9ce63018e..31d1d5f75 100644 --- a/readthedocs/restapi/views/core_views.py +++ b/readthedocs/restapi/views/core_views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utility endpoints relating to canonical urls, embedded content, etc.""" from django.shortcuts import get_object_or_404 diff --git a/readthedocs/restapi/views/search_views.py b/readthedocs/restapi/views/search_views.py index b799da215..da0dab870 100644 --- a/readthedocs/restapi/views/search_views.py +++ b/readthedocs/restapi/views/search_views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Endpoints related to searching through projects, sections, etc.""" import logging diff --git a/readthedocs/restapi/views/task_views.py b/readthedocs/restapi/views/task_views.py index a61e798a0..8bf9d3843 100644 --- a/readthedocs/restapi/views/task_views.py +++ b/readthedocs/restapi/views/task_views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Endpoints relating to task/job status, etc.""" import logging diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py index e6f3d81da..a9fe07b02 100644 --- a/readthedocs/search/indexes.py +++ b/readthedocs/search/indexes.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Search indexing classes to index into Elasticsearch. diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 9167e9418..a1f0cc209 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Utilities related to searching Elastic.""" from pprint import pprint diff --git a/readthedocs/search/signals.py b/readthedocs/search/signals.py index 60b4a4644..17cc36491 100644 --- a/readthedocs/search/signals.py +++ b/readthedocs/search/signals.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """We define custom Django signals to trigger before executing searches.""" import django.dispatch diff --git a/readthedocs/vcs_support/backends/__init__.py b/readthedocs/vcs_support/backends/__init__.py index 37dde39b6..0a022a545 100644 --- a/readthedocs/vcs_support/backends/__init__.py +++ b/readthedocs/vcs_support/backends/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Listing of all the VCS backends.""" from __future__ import absolute_import from . import bzr, hg, git, svn diff --git a/readthedocs/vcs_support/utils.py b/readthedocs/vcs_support/utils.py index 990016688..65f9e3839 100644 --- a/readthedocs/vcs_support/utils.py +++ b/readthedocs/vcs_support/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Locking utilities.""" import errno import logging diff --git a/readthedocs/wsgi.py b/readthedocs/wsgi.py index 9d35da5f3..997fb6285 100644 --- a/readthedocs/wsgi.py +++ b/readthedocs/wsgi.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """WSGI application helper.""" from __future__ import absolute_import From dceae9a0b4533c0faa5344b926394686743af90e Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Tue, 15 Jan 2019 17:47:06 -0500 Subject: [PATCH 32/65] Remove dead code from config module `DOCKER_IMAGE_SETTINGS` has the same value as `env_config`. It is used to update the python version supported for that image. --- readthedocs/config/config.py | 6 ------ readthedocs/doc_builder/config.py | 1 - 2 files changed, 7 deletions(-) diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 4b7c48f56..7eddb24f9 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -405,12 +405,6 @@ class BuildConfigV1(BuildConfigBase): self.env_config.update( DOCKER_IMAGE_SETTINGS[build['image']] ) - # Update docker settings from user config - if 'DOCKER_IMAGE_SETTINGS' in self.env_config and \ - build['image'] in self.env_config['DOCKER_IMAGE_SETTINGS']: - self.env_config.update( - self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']] - ) # Allow to override specific project config_image = self.defaults.get('build_image') diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py index 00a0095bc..4f755f65f 100644 --- a/readthedocs/doc_builder/config.py +++ b/readthedocs/doc_builder/config.py @@ -59,7 +59,6 @@ def load_yaml_config(version): img_settings = DOCKER_IMAGE_SETTINGS.get(img_name, None) if img_settings: env_config.update(img_settings) - env_config['DOCKER_IMAGE_SETTINGS'] = img_settings try: config = load_config( From 17d443c6140a94bf8796903e5b0d0b88265bb666 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 16 Jan 2019 09:40:46 +0100 Subject: [PATCH 33/65] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5095d4a54..e578b9961 100644 --- a/README.rst +++ b/README.rst @@ -78,6 +78,6 @@ when you push to GitHub. License ------- -`MIT`_ © 2010-2017 Read the Docs, Inc & contributors +`MIT`_ © 2010-2019 Read the Docs, Inc & contributors .. _MIT: LICENSE From 2df203ee98e79a744719ba6b581f48dd604e395f Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 16 Jan 2019 11:45:27 +0100 Subject: [PATCH 34/65] Validate mkdocs.yml config on values that we manipulate Make sure that the values we will manipulate from the mkdocs.yml file the correct type we need them to be. If we found a problem with them, we report a proper message to user to debug it by themselves. --- readthedocs/doc_builder/backends/mkdocs.py | 24 +++++++++++-- readthedocs/doc_builder/exceptions.py | 10 ++++++ .../rtd_tests/tests/test_doc_builder.py | 34 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 6119673b8..67df595bd 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -84,7 +84,7 @@ class BaseMkdocs(BaseBuilder): """ Load a YAML config. - Raise BuildEnvironmentError if failed due to syntax errors. + :raises: ``MkDocsYAMLParseError`` if failed due to syntax errors. """ try: return yaml.safe_load( @@ -105,7 +105,12 @@ class BaseMkdocs(BaseBuilder): ) def append_conf(self, **__): - """Set mkdocs config values.""" + """ + Set mkdocs config values. + + :raises: ``MkDocsYAMLParseError`` if failed due to known type errors + (i.e. expecting a list and a string is found). + """ if not self.yaml_file: self.yaml_file = os.path.join(self.root_path, 'mkdocs.yml') @@ -113,12 +118,27 @@ class BaseMkdocs(BaseBuilder): # Handle custom docs dirs user_docs_dir = user_config.get('docs_dir') + if not isinstance(user_docs_dir, (type(None), str)): + raise MkDocsYAMLParseError( + MkDocsYAMLParseError.INVALID_DOCS_DIR_CONFIG, + ) + docs_dir = self.docs_dir(docs_dir=user_docs_dir) self.create_index(extension='md') user_config['docs_dir'] = docs_dir # Set mkdocs config values static_url = get_absolute_static_url() + + for config in ('extra_css', 'extra_javascript'): + user_value = user_config.get(config, []) + if not isinstance(user_value, list): + raise MkDocsYAMLParseError( + MkDocsYAMLParseError.INVALID_EXTRA_CONFIG.format( + config=config, + ), + ) + user_config.setdefault('extra_javascript', []).extend([ 'readthedocs-data.js', '%score/js/readthedocs-doc-embed.js' % static_url, diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index ce2ce3d84..362b18015 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -59,3 +59,13 @@ class MkDocsYAMLParseError(BuildEnvironmentError): GENERIC_WITH_PARSE_EXCEPTION = ugettext_noop( 'Problem parsing MkDocs YAML configuration. {exception}', ) + + INVALID_DOCS_DIR_CONFIG = ugettext_noop( + 'The "docs_dir" config from your MkDocs YAML config file has to be a ' + 'string with relative or absolute path.', + ) + + INVALID_EXTRA_CONFIG = ugettext_noop( + 'The "{config}" config from your MkDocs YAML config file has to be a ' + 'a list of relative paths.', + ) diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index a384f5d00..b85226793 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -18,6 +18,7 @@ from mock import patch from readthedocs.builds.models import Version from readthedocs.doc_builder.backends.mkdocs import MkdocsHTML from readthedocs.doc_builder.backends.sphinx import BaseSphinx +from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Feature, Project @@ -388,6 +389,39 @@ class MkdocsBuilderTest(TestCase): 'mkdocs' ) + @patch('readthedocs.doc_builder.base.BaseBuilder.run') + @patch('readthedocs.projects.models.Project.checkout_path') + def test_append_conf_existing_yaml_on_root_with_invalid_setting(self, checkout_path, run): + tmpdir = tempfile.mkdtemp() + os.mkdir(os.path.join(tmpdir, 'docs')) + yaml_file = os.path.join(tmpdir, 'mkdocs.yml') + checkout_path.return_value = tmpdir + + python_env = Virtualenv( + version=self.version, + build_env=self.build_env, + config=None, + ) + self.searchbuilder = MkdocsHTML( + build_env=self.build_env, + python_env=python_env, + ) + + # We can't use ``@pytest.mark.parametrize`` on a Django test case + yaml_contents = [ + {'docs_dir': ['docs']}, + {'extra_css': 'a string here'}, + {'extra_javascript': None}, + ] + for content in yaml_contents: + yaml.safe_dump( + content, + open(yaml_file, 'w'), + ) + with self.assertRaises(MkDocsYAMLParseError): + self.searchbuilder.append_conf() + + @patch('readthedocs.doc_builder.base.BaseBuilder.run') @patch('readthedocs.projects.models.Project.checkout_path') def test_dont_override_theme(self, checkout_path, run): From 3fc254fcde1282e151b15652bb0424fa2a337d17 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 10 Jan 2019 12:31:48 +0100 Subject: [PATCH 35/65] Support custom robots.txt Check for a custom `robots.txt` on the default version and if it does exist serve it. Otherwise, return 404. --- readthedocs/core/urls/subdomain.py | 6 ++++- readthedocs/core/views/serve.py | 35 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 826c64436..56f981de7 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -10,7 +10,7 @@ from django.conf.urls.static import static from readthedocs.core.views.serve import ( redirect_page_with_filename, - redirect_project_slug, serve_docs + redirect_project_slug, serve_docs, robots_txt, ) from readthedocs.core.views import ( server_error_500, @@ -22,6 +22,10 @@ handler500 = server_error_500 handler404 = server_error_404 subdomain_urls = [ + url((r'robots.txt$'.format(**pattern_opts)), + robots_txt, + name='robots_txt'), + url(r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), redirect_page_with_filename, diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index f7741631e..9a0aff804 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -223,3 +223,38 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''): raise Http404( 'File not found. Tried these files: %s' % ','.join(files_tried)) + + +@map_project_slug +def robots_txt(request, project): + """ + Serve custom user's defined ``/robots.txt``. + + If the user added a ``robots.txt`` in the "default version" of the project, + we serve it directly. + """ + if project.privacy_level == constants.PRIVATE: + # If project is private, there is nothing to communicate to the bots. + raise Http404() + + # Use the ``robots.txt`` file from the default version configured + version_slug = project.get_default_version() + + filename = resolve_path( + project, + version_slug=version_slug, + filename='robots.txt', + subdomain=True, # subdomain will make it a "full" path without a URL prefix + ) + + # This breaks path joining, by ignoring the root when given an "absolute" path + if filename[0] == '/': + filename = filename[1:] + + basepath = PublicSymlink(project).project_root + fullpath = os.path.join(basepath, filename) + + if os.path.exists(fullpath): + return HttpResponse(open(fullpath).read(), content_type='text/plain') + + raise Http404() From 5d2c24c69c29dd6b17dfa3d42f840080be482e33 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 11:15:02 +0100 Subject: [PATCH 36/65] Proper URL formatting --- readthedocs/core/urls/subdomain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 56f981de7..23f155324 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + """URL configurations for subdomains.""" from __future__ import absolute_import @@ -22,9 +24,7 @@ handler500 = server_error_500 handler404 = server_error_404 subdomain_urls = [ - url((r'robots.txt$'.format(**pattern_opts)), - robots_txt, - name='robots_txt'), + url(r'robots.txt$', robots_txt, name='robots_txt'), url(r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), From 65c89042cab8de6f4ad2ac6a5c1fbd3c649e6e65 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 11:15:27 +0100 Subject: [PATCH 37/65] Explicit Allow/Disallow instead of 404 --- readthedocs/core/views/serve.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 9a0aff804..828b6bb9a 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -234,8 +234,8 @@ def robots_txt(request, project): we serve it directly. """ if project.privacy_level == constants.PRIVATE: - # If project is private, there is nothing to communicate to the bots. - raise Http404() + # If project is private, we disallow the whole site + raise HttpResponse('User-agent: *\nDisallow: /\n') # Use the ``robots.txt`` file from the default version configured version_slug = project.get_default_version() @@ -257,4 +257,4 @@ def robots_txt(request, project): if os.path.exists(fullpath): return HttpResponse(open(fullpath).read(), content_type='text/plain') - raise Http404() + raise HttpResponse('User-agent: *\nAllow: /\n') From 52588a39bc3cafa60a00d264f1a0a2143e6019e1 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 11:52:58 +0100 Subject: [PATCH 38/65] FAQ documentation for robots.txt file --- docs/faq.rst | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/faq.rst b/docs/faq.rst index 30025963c..da08605d9 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -230,3 +230,49 @@ What commit of Read the Docs is in production? ---------------------------------------------- We deploy readthedocs.org from the `rel` branch in our GitHub repository. You can see the latest commits that have been deployed by looking on GitHub: https://github.com/rtfd/readthedocs.org/commits/rel + + +How can I avoid bot crawlers to show a deprecated version of my docs? +--------------------------------------------------------------------- + +If readers search something related to your docs in Google, it will probably return the most relevant version of your documentation. +It may happen that this version is already deprecated and you want to stop Google indexing it as a result, +and start suggesting the latest (or newer) one. + +To accomplish this, you can add a ``robots.txt`` file to your documentation's root so it ends up served at the root URL of your project +(for example, https://yourproject.readthedocs.io/robots.txt). + + +Minimal example of ``robots.txt`` ++++++++++++++++++++++++++++++++++ + +:: + + User-agent: * + Disallow: /en/deprecated-version/ + Disallow: /en/2.0/ + +.. note:: + + See `Google's docs`_ for its full syntax. + +This file has to be served as is under ``/robots.txt``. +Depending if you are using Sphinx or MkDocs, you will need a different configuration for this. + + +Sphinx +~~~~~~ + +Sphinx uses `html_extra`_ option to add static files to the output. +You need to create a ``robots.txt`` file and put it under the path defined in ``html_extra``. + + +MkDocs +~~~~~~ + +MkDocs needs the ``robots.txt`` to be at the directory defined at `docs_dir`_ config. + + +.. _Google's docs: https://support.google.com/webmasters/answer/6062608 +.. _html_extra: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_extra_path +.. _docs_dir: https://www.mkdocs.org/user-guide/configuration/#docs_dir From 988c46bf34cad9fe0c12959e0f860ed25bd9e0cf Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 16:52:15 +0100 Subject: [PATCH 39/65] Use return for HttpResponse --- readthedocs/core/views/serve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 828b6bb9a..5d852bc2d 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -257,4 +257,4 @@ def robots_txt(request, project): if os.path.exists(fullpath): return HttpResponse(open(fullpath).read(), content_type='text/plain') - raise HttpResponse('User-agent: *\nAllow: /\n') + return HttpResponse('User-agent: *\nAllow: /\n') From 5ccc878ee5e98ef683922c6d060ba2a9d453a6e2 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 16:52:35 +0100 Subject: [PATCH 40/65] Do not serve the robots.txt file if private or version not active/built --- readthedocs/core/views/serve.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 5d852bc2d..27e599780 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -233,12 +233,23 @@ def robots_txt(request, project): If the user added a ``robots.txt`` in the "default version" of the project, we serve it directly. """ - if project.privacy_level == constants.PRIVATE: - # If project is private, we disallow the whole site - raise HttpResponse('User-agent: *\nDisallow: /\n') - # Use the ``robots.txt`` file from the default version configured version_slug = project.get_default_version() + version = project.versions.get(slug=version_slug) + + no_serve_robots_txt = any( + # If project is private or, + project.privacy_level == constants.PRIVATE, + # default version is private or, + version.privacy_level == constants.PRIVATE, + # default version is not active or, + not version.active, + # default version is not built + not version.built, + ) + if no_serve_robots_txt: + # ... we do return a 404 + raise Http404() filename = resolve_path( project, From 38d812080fca6727d7c80f999f1067e0e96d48d6 Mon Sep 17 00:00:00 2001 From: Eric Holscher <25510+ericholscher@users.noreply.github.com> Date: Mon, 14 Jan 2019 16:02:45 +0100 Subject: [PATCH 41/65] Rephrase question Co-Authored-By: humitos --- docs/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index da08605d9..c78263201 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -232,7 +232,7 @@ What commit of Read the Docs is in production? We deploy readthedocs.org from the `rel` branch in our GitHub repository. You can see the latest commits that have been deployed by looking on GitHub: https://github.com/rtfd/readthedocs.org/commits/rel -How can I avoid bot crawlers to show a deprecated version of my docs? +How can I avoid search results having a deprecated version of my docs? --------------------------------------------------------------------- If readers search something related to your docs in Google, it will probably return the most relevant version of your documentation. From 47248113d7220bf2cbfa83cf3ab5854fda3d8e20 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Jan 2019 17:13:35 +0100 Subject: [PATCH 42/65] Add content_type to default response --- readthedocs/core/views/serve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 27e599780..4ac2c7e86 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -268,4 +268,4 @@ def robots_txt(request, project): if os.path.exists(fullpath): return HttpResponse(open(fullpath).read(), content_type='text/plain') - return HttpResponse('User-agent: *\nAllow: /\n') + return HttpResponse('User-agent: *\nAllow: /\n', content_type='text/plain') From 19b52092e21de6f616ba7cd50d67688bcccb043d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 16 Jan 2019 16:51:10 +0100 Subject: [PATCH 43/65] any() call fixed --- readthedocs/core/views/serve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 4ac2c7e86..2b24b45d9 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -237,7 +237,7 @@ def robots_txt(request, project): version_slug = project.get_default_version() version = project.versions.get(slug=version_slug) - no_serve_robots_txt = any( + no_serve_robots_txt = any([ # If project is private or, project.privacy_level == constants.PRIVATE, # default version is private or, @@ -246,7 +246,7 @@ def robots_txt(request, project): not version.active, # default version is not built not version.built, - ) + ]) if no_serve_robots_txt: # ... we do return a 404 raise Http404() From 3e4b1a43b00be793f4b9b755c5a9dc94e2d8a5c3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 16 Jan 2019 16:51:30 +0100 Subject: [PATCH 44/65] Tests for serving default and custom robots.txt file (public/private) --- .../rtd_tests/tests/test_doc_serving.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index 9a5176aec..56798ad84 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -2,13 +2,17 @@ from __future__ import absolute_import, unicode_literals, division, print_function import mock +from mock import patch, mock_open import django_dynamic_fixture as fixture +import pytest +import six from django.contrib.auth.models import User from django.test import TestCase from django.test.utils import override_settings from django.http import Http404 from django.conf import settings +from django.urls import reverse from readthedocs.rtd_tests.base import RequestFactoryTestMixin from readthedocs.projects import constants @@ -77,6 +81,28 @@ class TestPrivateDocs(BaseDocServing): self.assertTrue('private_web_root' in str(exc.exception)) self.assertTrue('public_web_root' not in str(exc.exception)) + @override_settings( + PYTHON_MEDIA=False, + USE_SUBDOMAIN=True, + PUBLIC_DOMAIN='readthedocs.io', + ROOT_URLCONF=settings.SUBDOMAIN_URLCONF, + ) + def test_robots_txt(self): + self.public.versions.update(active=True, built=True) + response = self.client.get( + reverse('robots_txt'), + HTTP_HOST='private.readthedocs.io', + ) + self.assertEqual(response.status_code, 404) + + self.client.force_login(self.eric) + response = self.client.get( + reverse('robots_txt'), + HTTP_HOST='private.readthedocs.io', + ) + # Private projects/versions always return 404 for robots.txt + self.assertEqual(response.status_code, 404) + @override_settings(SERVE_DOCS=[constants.PRIVATE, constants.PUBLIC]) class TestPublicDocs(BaseDocServing): @@ -110,3 +136,41 @@ class TestPublicDocs(BaseDocServing): _serve_symlink_docs(request, project=self.private, filename='/en/latest/usage.html', privacy_level='public') self.assertTrue('private_web_root' not in str(exc.exception)) self.assertTrue('public_web_root' in str(exc.exception)) + + @override_settings( + PYTHON_MEDIA=False, + USE_SUBDOMAIN=True, + PUBLIC_DOMAIN='readthedocs.io', + ROOT_URLCONF=settings.SUBDOMAIN_URLCONF, + ) + def test_default_robots_txt(self): + self.public.versions.update(active=True, built=True) + response = self.client.get( + reverse('robots_txt'), + HTTP_HOST='public.readthedocs.io', + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'User-agent: *\nAllow: /\n') + + @override_settings( + PYTHON_MEDIA=False, + USE_SUBDOMAIN=True, + PUBLIC_DOMAIN='readthedocs.io', + ROOT_URLCONF=settings.SUBDOMAIN_URLCONF, + ) + @patch( + 'builtins.open', + new_callable=mock_open, + read_data='My own robots.txt', + ) + @patch('readthedocs.core.views.serve.os') + @pytest.mark.skipif(six.PY2, reason='In Python2 the mock is __builtins__.open') + def test_custom_robots_txt(self, os_mock, open_mock): + os_mock.path.exists.return_value = True + self.public.versions.update(active=True, built=True) + response = self.client.get( + reverse('robots_txt'), + HTTP_HOST='public.readthedocs.io', + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'My own robots.txt') From 4acfaa58952945bb3166f82bdd7fd894be005bac Mon Sep 17 00:00:00 2001 From: Hamdy Date: Thu, 17 Jan 2019 15:30:40 +0300 Subject: [PATCH 45/65] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 2447f29c3..37484f246 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2017 Read the Docs, Inc & contributors +Copyright (c) 2010-2019 Read the Docs, Inc & contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation From 0e6f84fdc526d5cebc72f72f2253454a93fa389e Mon Sep 17 00:00:00 2001 From: Xavier Fernandez Date: Fri, 18 Jan 2019 10:36:37 +0100 Subject: [PATCH 46/65] Fix integration typos --- docs/webhooks.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/webhooks.rst b/docs/webhooks.rst index 50ef2f326..7d3702f12 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -161,9 +161,9 @@ that your VCS provider is not configured correctly. If there is payload information on your Read the Docs project, you might need to verify that your versions are configured to build correctly. -Either way, it may help to either resync your webhook intergration (see +Either way, it may help to either resync your webhook integration (see `Resyncing webhooks`_ for information on this process), or set up an entirely -new webhook intergration. +new webhook integration. .. _webhook-github-services: @@ -180,7 +180,7 @@ In order for your project to continue automatically building, you will need to configure your GitHub repository with a new webhook. You can use either a connected GitHub account and a :ref:`GitHub webhook integration ` on your Read the Docs project, or you can use a -:ref:`generic webhook integraiton ` without a connected +:ref:`generic webhook integration ` without a connected account. .. [1] https://developer.github.com/changes/2018-04-25-github-services-deprecation/ From 0c48457b2b7091f54b58b32ca9bf27743f64b224 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Sat, 19 Jan 2019 21:36:52 +0100 Subject: [PATCH 47/65] Configuration file for ProBot Mergeable Bot --- .github/mergeable.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/mergeable.yml diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 000000000..6027bbb2d --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,28 @@ +# ProBot Mergeable Bot +# https://github.com/jusx/mergeable + +mergeable: + pull_requests: + approvals: + # Minimum of approvals needed. + min: 1 + message: 'The PR must have a minimum of 1 approvals.' + + description: + no_empty: + # Do not allow empty descriptions on PR. + enabled: false + message: 'Description can not be empty.' + + must_exclude: + # Do not allow 'DO NOT MERGE' phrase on PR's description. + regex: 'DO NOT MERGE' + message: 'Description says that the PR should not be merged yet.' + + # Do not allow 'WIP' on PR's title. + title: 'WIP' + + label: + # Do not allow PR with label 'PR: work in progress' + must_exclude: 'PR: work in progress' + message: 'This PR is work in progress.' From 8ccb6baa34d39bebff34b5aff04ad3ccda3f473b Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 21 Jan 2019 10:33:28 -0500 Subject: [PATCH 48/65] Fix local-docs-build requirements Our docs are currently not building because they depend on the RTD app being installed properly to import strings. This fixes the local-docs-build.txt file to include the proper requirements --- requirements/local-docs-build.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements/local-docs-build.txt b/requirements/local-docs-build.txt index 3f36eec20..c9ce6ed62 100644 --- a/requirements/local-docs-build.txt +++ b/requirements/local-docs-build.txt @@ -1,3 +1,5 @@ +-r pip.txt + # Base packages docutils==0.14 Sphinx==1.8.3 @@ -19,5 +21,4 @@ sphinx-prompt==1.0.0 # commonmark 0.5.5 is the latest version compatible with our docs, the # newer ones make `tox -e docs` to fail commonmark==0.5.5 - recommonmark==0.4.0 From ecd753d31e17597abfa88a4054193c88a670e3da Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Mon, 21 Jan 2019 11:03:36 -0500 Subject: [PATCH 49/65] Fix requirements for local installation pip install -r requirements.txt on a clean installation doesn't install all the requirements. This wasn't detected by travis because we install from requirements/testing.txt pyyaml was a dependency of yamale. --- requirements/lint.txt | 1 - requirements/pip.txt | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements/lint.txt b/requirements/lint.txt index d4a30debe..5295543b4 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -14,4 +14,3 @@ pylint-celery==0.3 prospector==1.1.6.2 # prospector 1.1.6.2 is not compatible with 2.0.0 pyflakes<2.0.0 -Pygments==2.3.1 diff --git a/requirements/pip.txt b/requirements/pip.txt index 6f815c9c3..f280b91ca 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -29,6 +29,8 @@ requests-toolbelt==0.8.0 slumber==0.7.1 lxml==4.2.5 defusedxml==0.5.0 +pyyaml==3.13 +Pygments==2.3.1 # Basic tools # Redis 3.x has an incompatible change and fails From 0f7b8d7f5e254a15cb04e5dc47a211d08a402646 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:09:07 +0100 Subject: [PATCH 50/65] pyupgrade over new code with conflicts --- readthedocs/builds/models.py | 18 ++--- readthedocs/config/config.py | 14 ++-- readthedocs/config/tests/test_config.py | 14 ++-- readthedocs/core/views/hooks.py | 8 +-- readthedocs/core/views/serve.py | 2 +- readthedocs/projects/admin.py | 6 +- readthedocs/projects/forms.py | 66 +++++++++---------- readthedocs/projects/models.py | 28 ++++---- readthedocs/projects/views/private.py | 2 +- readthedocs/rtd_tests/tests/test_celery.py | 4 +- .../rtd_tests/tests/test_notifications.py | 4 +- .../rtd_tests/tests/test_privacy_urls.py | 10 +-- 12 files changed, 88 insertions(+), 88 deletions(-) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 70ac5ed19..d4b58bb59 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -114,7 +114,7 @@ class Version(models.Model): objects = VersionManager.from_queryset(VersionQuerySet)() - class Meta(object): + class Meta: unique_together = [('project', 'slug')] ordering = ['-verbose_name'] permissions = ( @@ -203,7 +203,7 @@ class Version(models.Model): def save(self, *args, **kwargs): # pylint: disable=arguments-differ """Add permissions to the Version for all owners on save.""" from readthedocs.projects import tasks - obj = super(Version, self).save(*args, **kwargs) + obj = super().save(*args, **kwargs) for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( @@ -219,7 +219,7 @@ class Version(models.Model): args=[self.get_artifact_paths()], ) project_pk = self.project.pk - super(Version, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) broadcast( type='app', task=tasks.symlink_project, @@ -442,7 +442,7 @@ class APIVersion(Version): del kwargs[key] except KeyError: pass - super(APIVersion, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, *args, **kwargs): return 0 @@ -487,13 +487,13 @@ class Build(models.Model): CONFIG_KEY = '__config' - class Meta(object): + class Meta: ordering = ['-date'] get_latest_by = 'date' index_together = [['version', 'state', 'type']] def __init__(self, *args, **kwargs): - super(Build, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._config_changed = False @property @@ -557,7 +557,7 @@ class Build(models.Model): self._config and self._config == previous.config): previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} - super(Build, self).save(*args, **kwargs) + super().save(*args, **kwargs) self._config_changed = False def __str__(self): @@ -579,7 +579,7 @@ class Build(models.Model): return self.state == BUILD_STATE_FINISHED -class BuildCommandResultMixin(object): +class BuildCommandResultMixin: """ Mixin for common command result methods/properties. @@ -619,7 +619,7 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): start_time = models.DateTimeField(_('Start time')) end_time = models.DateTimeField(_('End time')) - class Meta(object): + class Meta: ordering = ['start_time'] get_latest_by = 'start_time' diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 223bd7b0a..5f7cd382f 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -79,7 +79,7 @@ class ConfigError(Exception): def __init__(self, message, code): self.code = code - super(ConfigError, self).__init__(message) + super().__init__(message) class ConfigOptionNotSupportedError(ConfigError): @@ -91,7 +91,7 @@ class ConfigOptionNotSupportedError(ConfigError): template = ( 'The "{}" configuration option is not supported in this version' ) - super(ConfigOptionNotSupportedError, self).__init__( + super().__init__( template.format(self.configuration), CONFIG_NOT_SUPPORTED ) @@ -112,10 +112,10 @@ class InvalidConfig(ConfigError): code=code, error=error_message, ) - super(InvalidConfig, self).__init__(message, code=code) + super().__init__(message, code=code) -class BuildConfigBase(object): +class BuildConfigBase: """ Config that handles the build of one particular documentation. @@ -223,7 +223,7 @@ class BuildConfigBase(object): @property def python_interpreter(self): ver = self.python_full_version - return 'python{0}'.format(ver) + return 'python{}'.format(ver) @property def python_full_version(self): @@ -417,7 +417,7 @@ class BuildConfigV1(BuildConfigBase): # Try to convert strings to an int first, to catch '2', then # a float, to catch '2.7' version = raw_python['version'] - if isinstance(version, six.string_types): + if isinstance(version, str): try: version = int(version) except ValueError: @@ -650,7 +650,7 @@ class BuildConfigV2(BuildConfigBase): python = {} with self.catch_validation_error('python.version'): version = self.pop_config('python.version', 3) - if isinstance(version, six.string_types): + if isinstance(version, str): try: version = int(version) except ValueError: diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 593453d8c..0d223a671 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -211,7 +211,7 @@ def test_python_pip_install_default(): assert build.python.install_with_pip is False -class TestValidatePythonExtraRequirements(object): +class TestValidatePythonExtraRequirements: def test_it_defaults_to_list(self): build = get_build_config({'python': {}}) @@ -243,7 +243,7 @@ class TestValidatePythonExtraRequirements(object): validate_string.assert_any_call('tests') -class TestValidateUseSystemSitePackages(object): +class TestValidateUseSystemSitePackages: def test_it_defaults_to_false(self): build = get_build_config({'python': {}}) @@ -269,7 +269,7 @@ class TestValidateUseSystemSitePackages(object): validate_bool.assert_any_call('to-validate') -class TestValidateSetupPyInstall(object): +class TestValidateSetupPyInstall: def test_it_defaults_to_false(self): build = get_build_config({'python': {}}) @@ -295,7 +295,7 @@ class TestValidateSetupPyInstall(object): validate_bool.assert_any_call('to-validate') -class TestValidatePythonVersion(object): +class TestValidatePythonVersion: def test_it_defaults_to_a_valid_version(self): build = get_build_config({'python': {}}) @@ -386,7 +386,7 @@ class TestValidatePythonVersion(object): assert build.python.version == value -class TestValidateFormats(object): +class TestValidateFormats: def test_it_defaults_to_empty(self): build = get_build_config({}) @@ -459,7 +459,7 @@ def test_valid_build_config(): assert build.python.use_system_site_packages is False -class TestValidateBuild(object): +class TestValidateBuild: def test_it_fails_if_build_is_invalid_option(self, tmpdir): apply_fs(tmpdir, yaml_config_dir) @@ -704,7 +704,7 @@ def test_as_dict(tmpdir): assert build.as_dict() == expected_dict -class TestBuildConfigV2(object): +class TestBuildConfigV2: def get_build_config( self, config, env_config=None, source_file='readthedocs.yml'): diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index c6d4bc911..6ed9855b6 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -156,14 +156,14 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: - msg = '(URL Build) Build Started: %s [%s]' % ( + msg = '(URL Build) Build Started: {} [{}]'.format( url, ' '.join(built)) log_info(project_slug, msg=msg) ret += msg for project_slug, not_building in list(all_not_building.items()): if not_building: - msg = '(URL Build) Not Building: %s [%s]' % ( + msg = '(URL Build) Not Building: {} [{}]'.format( url, ' '.join(not_building)) log_info(project_slug, msg=msg) ret += msg @@ -300,7 +300,7 @@ def bitbucket_build(request): repository = data['repository'] if not repository['absolute_url']: return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org{0}'.format( + search_url = 'bitbucket.org{}'.format( repository['absolute_url'].rstrip('/') ) elif version == 2: @@ -309,7 +309,7 @@ def bitbucket_build(request): for change in changes] if not data['repository']['full_name']: return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org/{0}'.format( + search_url = 'bitbucket.org/{}'.format( data['repository']['full_name'] ) except (TypeError, ValueError, KeyError): diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 2b24b45d9..168b1aac2 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -117,7 +117,7 @@ def redirect_page_with_filename(request, project, subproject, filename): # pyli def _serve_401(request, project): res = render(request, '401.html') res.status_code = 401 - log.debug('Unauthorized access to {0} documentation'.format(project.slug)) + log.debug('Unauthorized access to {} documentation'.format(project.slug)) return res diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 9f6da41dd..6bead7e35 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -164,12 +164,12 @@ class ProjectAdmin(GuardedModelAdmin): total += count else: messages.add_message(request, messages.ERROR, - 'Project has multiple owners: {0}'.format(project)) + 'Project has multiple owners: {}'.format(project)) if total == 0: messages.add_message(request, messages.ERROR, 'No users banned') else: messages.add_message(request, messages.INFO, - 'Banned {0} user(s)'.format(total)) + 'Banned {} user(s)'.format(total)) ban_owner.short_description = 'Ban project owner' @@ -190,7 +190,7 @@ class ProjectAdmin(GuardedModelAdmin): return delete_selected(self, request, queryset) def get_actions(self, request): - actions = super(ProjectAdmin, self).get_actions(request) + actions = super().get_actions(request) actions['delete_selected'] = ( self.__class__.delete_selected_and_artifacts, 'delete_selected', diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 70fcc88d7..6486472e1 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -65,17 +65,17 @@ class ProjectForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) - super(ProjectForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, commit=True): - project = super(ProjectForm, self).save(commit) + project = super().save(commit) if commit: if self.user and not project.users.filter(pk=self.user.pk).exists(): project.users.add(self.user) return project -class ProjectTriggerBuildMixin(object): +class ProjectTriggerBuildMixin: """ Mixin to trigger build on form save. @@ -86,7 +86,7 @@ class ProjectTriggerBuildMixin(object): def save(self, commit=True): """Trigger build on commit save.""" - project = super(ProjectTriggerBuildMixin, self).save(commit) + project = super().save(commit) if commit: trigger_build(project=project) return project @@ -103,7 +103,7 @@ class ProjectBasicsForm(ProjectForm): """Form for basic project fields.""" - class Meta(object): + class Meta: model = Project fields = ('name', 'repo', 'repo_type') @@ -114,7 +114,7 @@ class ProjectBasicsForm(ProjectForm): def __init__(self, *args, **kwargs): show_advanced = kwargs.pop('show_advanced', False) - super(ProjectBasicsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if show_advanced: self.fields['advanced'] = forms.BooleanField( required=False, @@ -125,7 +125,7 @@ class ProjectBasicsForm(ProjectForm): def save(self, commit=True): """Add remote repository relationship to the project instance.""" - instance = super(ProjectBasicsForm, self).save(commit) + instance = super().save(commit) remote_repo = self.cleaned_data.get('remote_repository', None) if remote_repo: if commit: @@ -178,7 +178,7 @@ class ProjectExtraForm(ProjectForm): """Additional project information form.""" - class Meta(object): + class Meta: model = Project fields = ( 'description', @@ -216,7 +216,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): 'environment.'), ) - class Meta(object): + class Meta: model = Project fields = ( # Standard build edits @@ -240,7 +240,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): ) def __init__(self, *args, **kwargs): - super(ProjectAdvancedForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) default_choice = (None, '-' * 9) all_versions = self.instance.versions.values_list( @@ -268,7 +268,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, ProjectExtraForm): - class Meta(object): + class Meta: model = Project fields = ( # Basics @@ -321,14 +321,14 @@ class ProjectRelationshipBaseForm(forms.ModelForm): parent = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = ProjectRelationship fields = '__all__' def __init__(self, *args, **kwargs): self.project = kwargs.pop('project') self.user = kwargs.pop('user') - super(ProjectRelationshipBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Don't display the update form with an editable child, as it will be # filtered out from the queryset anyways. if hasattr(self, 'instance') and self.instance.pk is not None: @@ -375,11 +375,11 @@ class DualCheckboxWidget(forms.CheckboxInput): """Checkbox with link to the version's built documentation.""" def __init__(self, version, attrs=None, check_test=bool): - super(DualCheckboxWidget, self).__init__(attrs, check_test) + super().__init__(attrs, check_test) self.version = version def render(self, name, value, attrs=None, renderer=None): - checkbox = super(DualCheckboxWidget, self).render(name, value, attrs, renderer) + checkbox = super().render(name, value, attrs, renderer) icon = self.render_icon() return mark_safe('{}{}'.format(checkbox, icon)) @@ -472,7 +472,7 @@ class BaseUploadHTMLForm(forms.Form): def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) - super(BaseUploadHTMLForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean(self): version_slug = self.cleaned_data['version'] @@ -512,7 +512,7 @@ class UserForm(forms.Form): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(UserForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_user(self): name = self.cleaned_data['user'] @@ -538,7 +538,7 @@ class EmailHookForm(forms.Form): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(EmailHookForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_email(self): self.email = EmailHook.objects.get_or_create( @@ -556,7 +556,7 @@ class WebHookForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(WebHookForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, commit=True): self.webhook = WebHook.objects.get_or_create( @@ -578,7 +578,7 @@ class TranslationBaseForm(forms.Form): def __init__(self, *args, **kwargs): self.parent = kwargs.pop('parent', None) self.user = kwargs.pop('user') - super(TranslationBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['project'].choices = self.get_choices() def get_choices(self): @@ -659,13 +659,13 @@ class RedirectForm(forms.ModelForm): """Form for project redirects.""" - class Meta(object): + class Meta: model = Redirect fields = ['redirect_type', 'from_url', 'to_url'] def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(RedirectForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, **_): # pylint: disable=arguments-differ # TODO this should respect the unused argument `commit`. It's not clear @@ -686,13 +686,13 @@ class DomainBaseForm(forms.ModelForm): project = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = Domain exclude = ['machine', 'cname', 'count'] # pylint: disable=modelform-uses-exclude def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(DomainBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_project(self): return self.project @@ -730,13 +730,13 @@ class IntegrationForm(forms.ModelForm): project = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = Integration exclude = ['provider_data', 'exchanges'] # pylint: disable=modelform-uses-exclude def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(IntegrationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Alter the integration type choices to only provider webhooks self.fields['integration_type'].choices = Integration.WEBHOOK_INTEGRATIONS # yapf: disable # noqa @@ -745,20 +745,20 @@ class IntegrationForm(forms.ModelForm): def save(self, commit=True): self.instance = Integration.objects.subclass(self.instance) - return super(IntegrationForm, self).save(commit) + return super().save(commit) class ProjectAdvertisingForm(forms.ModelForm): """Project promotion opt-out form.""" - class Meta(object): + class Meta: model = Project fields = ['allow_promos'] def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(ProjectAdvertisingForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class FeatureForm(forms.ModelForm): @@ -773,12 +773,12 @@ class FeatureForm(forms.ModelForm): feature_id = forms.ChoiceField() - class Meta(object): + class Meta: model = Feature fields = ['projects', 'feature_id', 'default_true'] def __init__(self, *args, **kwargs): - super(FeatureForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['feature_id'].choices = Feature.FEATURES @@ -792,13 +792,13 @@ class EnvironmentVariableForm(forms.ModelForm): project = forms.CharField(widget=forms.HiddenInput(), required=False) - class Meta(object): + class Meta: model = EnvironmentVariable fields = ('name', 'value', 'project') def __init__(self, *args, **kwargs): self.project = kwargs.pop('project', None) - super(EnvironmentVariableForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_project(self): return self.project diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index f4fdea8f0..78d7793a4 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -57,12 +57,12 @@ class ProjectRelationship(models.Model): objects = ChildRelatedProjectQuerySet.as_manager() def __str__(self): - return '%s -> %s' % (self.parent, self.child) + return '{} -> {}'.format(self.parent, self.child) def save(self, *args, **kwargs): # pylint: disable=arguments-differ if not self.alias: self.alias = self.child.slug - super(ProjectRelationship, self).save(*args, **kwargs) + super().save(*args, **kwargs) # HACK def get_absolute_url(self): @@ -252,7 +252,7 @@ class Project(models.Model): objects = ProjectQuerySet.as_manager() all_objects = models.Manager() - class Meta(object): + class Meta: ordering = ('slug',) permissions = ( # Translators: Permission around whether a user can view the @@ -271,7 +271,7 @@ class Project(models.Model): self.slug = slugify(self.name) if not self.slug: raise Exception(_('Model must have slug')) - super(Project, self).save(*args, **kwargs) + super().save(*args, **kwargs) for owner in self.users.all(): assign('view_project', owner, self) try: @@ -375,7 +375,7 @@ class Project(models.Model): settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug) if include_file: path = os.path.join( - path, '%s.%s' % (self.slug, type_.replace('htmlzip', 'zip'))) + path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip'))) return path def get_production_media_url(self, type_, version_slug, full_path=True): @@ -389,7 +389,7 @@ class Project(models.Model): except NoReverseMatch: return '' if full_path: - path = '//%s%s' % (settings.PRODUCTION_DOMAIN, path) + path = '//{}{}'.format(settings.PRODUCTION_DOMAIN, path) return path def subdomain(self): @@ -852,7 +852,7 @@ class APIProject(Project): del kwargs[key] except KeyError: pass - super(APIProject, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Overwrite the database property with the value from the API self.ad_free = ad_free @@ -899,7 +899,7 @@ class ImportedFile(models.Model): return resolve(project=self.project, version_slug=self.version.slug, filename=self.path) def __str__(self): - return '%s: %s' % (self.name, self.project) + return '{}: {}'.format(self.name, self.project) class Notification(models.Model): @@ -907,7 +907,7 @@ class Notification(models.Model): related_name='%(class)s_notifications') objects = RelatedProjectQuerySet.as_manager() - class Meta(object): + class Meta: abstract = True @@ -958,7 +958,7 @@ class Domain(models.Model): objects = RelatedProjectQuerySet.as_manager() - class Meta(object): + class Meta: ordering = ('-canonical', '-machine', 'domain') def __str__(self): @@ -971,7 +971,7 @@ class Domain(models.Model): self.domain = parsed.netloc else: self.domain = parsed.path - super(Domain, self).save(*args, **kwargs) + super().save(*args, **kwargs) broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk],) @@ -979,7 +979,7 @@ class Domain(models.Model): from readthedocs.projects import tasks broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk, True],) - super(Domain, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) @python_2_unicode_compatible @@ -1052,7 +1052,7 @@ class Feature(models.Model): objects = FeatureQuerySet.as_manager() def __str__(self): - return '{0} feature'.format( + return '{} feature'.format( self.get_feature_display(), ) @@ -1087,4 +1087,4 @@ class EnvironmentVariable(TimeStampedModel, models.Model): def save(self, *args, **kwargs): # pylint: disable=arguments-differ self.value = shlex_quote(self.value) - return super(EnvironmentVariable, self).save(*args, **kwargs) + return super().save(*args, **kwargs) diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 7a48331b1..81998a7e8 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -725,7 +725,7 @@ class DomainMixin(ProjectAdminMixin, PrivateViewMixin): class DomainList(DomainMixin, ListViewWithForm): def get_context_data(self, **kwargs): - ctx = super(DomainList, self).get_context_data(**kwargs) + ctx = super().get_context_data(**kwargs) # Retry validation on all domains if applicable for domain in ctx['domain_list']: diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index 7de03a97b..ccd2b70e3 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -31,7 +31,7 @@ class TestCeleryBuilding(RTDTestCase): def setUp(self): repo = make_test_git() self.repo = repo - super(TestCeleryBuilding, self).setUp() + super().setUp() self.eric = User(username='eric') self.eric.set_password('test') self.eric.save() @@ -45,7 +45,7 @@ class TestCeleryBuilding(RTDTestCase): def tearDown(self): shutil.rmtree(self.repo) - super(TestCeleryBuilding, self).tearDown() + super().tearDown() def test_remove_dirs(self): directory = mkdtemp() diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 1fa16323d..4230f8f3e 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -51,7 +51,7 @@ class NotificationTests(TestCase): self.assertEqual(notify.get_template_names('site'), ['builds/notifications/foo_site.html']) self.assertEqual(notify.get_subject(), - 'This is {0}'.format(build.id)) + 'This is {}'.format(build.id)) self.assertEqual(notify.get_context_data(), {'foo': build, 'production_uri': 'https://readthedocs.org', @@ -90,7 +90,7 @@ class NotificationBackendTests(TestCase): request=mock.ANY, template='core/email/common.txt', context={'content': 'Test'}, - subject=u'This is {}'.format(build.id), + subject='This is {}'.format(build.id), template_html='core/email/common.html', recipient=user.email, ) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index 937574fba..52ec89d74 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -19,7 +19,7 @@ from readthedocs.oauth.models import RemoteRepository, RemoteOrganization from readthedocs.rtd_tests.utils import create_user -class URLAccessMixin(object): +class URLAccessMixin: default_kwargs = {} response_data = {} @@ -93,10 +93,10 @@ class URLAccessMixin(object): for not_obj in self.context_data: if isinstance(obj, list) or isinstance(obj, set) or isinstance(obj, tuple): self.assertNotIn(not_obj, obj) - print("%s not in %s" % (not_obj, obj)) + print("{} not in {}".format(not_obj, obj)) else: self.assertNotEqual(not_obj, obj) - print("%s is not %s" % (not_obj, obj)) + print("{} is not {}".format(not_obj, obj)) def _test_url(self, urlpatterns): deconstructed_urls = extract_views_from_urlpatterns(urlpatterns) @@ -134,7 +134,7 @@ class URLAccessMixin(object): class ProjectMixin(URLAccessMixin): def setUp(self): - super(ProjectMixin, self).setUp() + super().setUp() self.build = get(Build, project=self.pip) self.tag = get(Tag, slug='coolness') self.subproject = get(Project, slug='sub', language='ja', @@ -313,7 +313,7 @@ class PrivateProjectUnauthAccessTest(PrivateProjectMixin, TestCase): class APIMixin(URLAccessMixin): def setUp(self): - super(APIMixin, self).setUp() + super().setUp() self.build = get(Build, project=self.pip) self.build_command_result = get(BuildCommandResult, project=self.pip) self.domain = get(Domain, url='http://docs.foobar.com', project=self.pip) From 1ffc03e14747cffad0aae0f3fd2bf271f7ea95c3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:13:20 +0100 Subject: [PATCH 51/65] isort new files --- readthedocs/builds/models.py | 11 ++--------- readthedocs/config/config.py | 5 +---- readthedocs/core/urls/subdomain.py | 16 +++++++--------- readthedocs/core/views/hooks.py | 8 +------- readthedocs/core/views/serve.py | 9 +++------ readthedocs/projects/admin.py | 7 ------- readthedocs/projects/forms.py | 21 +-------------------- readthedocs/projects/models.py | 21 ++++++++++++--------- readthedocs/projects/notifications.py | 5 +++-- readthedocs/projects/signals.py | 1 - readthedocs/projects/tasks.py | 2 +- readthedocs/projects/urls/private.py | 10 ++-------- readthedocs/projects/views/private.py | 1 + 13 files changed, 34 insertions(+), 83 deletions(-) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index d4b58bb59..eaf4614a1 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1,21 +1,14 @@ # -*- coding: utf-8 -*- """Models for the builds app.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import logging import os.path import re from shutil import rmtree -from builtins import object from django.conf import settings from django.db import models +from django.urls import reverse from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext @@ -23,7 +16,6 @@ from django.utils.translation import ugettext_lazy as _ from guardian.shortcuts import assign from jsonfield import JSONField from taggit.managers import TaggableManager -from django.urls import reverse from readthedocs.core.utils import broadcast from readthedocs.projects.constants import ( @@ -55,6 +47,7 @@ from .utils import ( ) from .version_slug import VersionSlugField + DEFAULT_VERSION_PRIVACY_LEVEL = getattr( settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public') diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 5f7cd382f..c860b018c 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -3,13 +3,9 @@ # pylint: disable=too-many-lines """Build configuration for rtd.""" -from __future__ import division, print_function, unicode_literals - import os from contextlib import contextmanager -import six - from readthedocs.projects.constants import DOCUMENTATION_CHOICES from .find import find_one @@ -26,6 +22,7 @@ from .validation import ( validate_string, ) + __all__ = ( 'ALL', 'load', diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 23f155324..75df07eb3 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -1,24 +1,22 @@ # -*- coding: utf-8 -*- """URL configurations for subdomains.""" -from __future__ import absolute_import - from functools import reduce from operator import add -from django.conf.urls import url from django.conf import settings +from django.conf.urls import url from django.conf.urls.static import static +from readthedocs.constants import pattern_opts +from readthedocs.core.views import server_error_404, server_error_500 from readthedocs.core.views.serve import ( redirect_page_with_filename, - redirect_project_slug, serve_docs, robots_txt, + redirect_project_slug, + robots_txt, + serve_docs, ) -from readthedocs.core.views import ( - server_error_500, - server_error_404, -) -from readthedocs.constants import pattern_opts + handler500 = server_error_500 handler404 = server_error_404 diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 6ed9855b6..624f325d9 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,12 +1,5 @@ """Views pertaining to builds.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - import json import logging import re @@ -21,6 +14,7 @@ from readthedocs.projects import constants from readthedocs.projects.models import Feature, Project from readthedocs.projects.tasks import sync_repository_task + log = logging.getLogger(__name__) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 168b1aac2..fa5245cfe 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -25,18 +25,14 @@ PYTHON_MEDIA (False) - Set this to True to serve docs & media from Python SERVE_DOCS (['private']) - The list of ['private', 'public'] docs to serve. """ -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import logging import mimetypes import os from functools import wraps from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import get_object_or_404 -from django.shortcuts import render +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render from django.utils.encoding import iri_to_uri from django.views.static import serve @@ -47,6 +43,7 @@ from readthedocs.core.symlink import PrivateSymlink, PublicSymlink from readthedocs.projects import constants from readthedocs.projects.models import Project, ProjectRelationship + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 6bead7e35..95e7f3163 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -1,13 +1,6 @@ # -*- coding: utf-8 -*- """Django administration interface for `projects.models`""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.contrib import admin, messages from django.contrib.admin.actions import delete_selected from django.utils.translation import ugettext_lazy as _ diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 6486472e1..4cc9f0ae4 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -2,28 +2,9 @@ """Project forms.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - -try: - # TODO: remove this when we deprecate Python2 - # re.fullmatch is >= Py3.4 only - from re import fullmatch -except ImportError: - # https://stackoverflow.com/questions/30212413/backport-python-3-4s-regular-expression-fullmatch-to-python-2 - import re - - def fullmatch(regex, string, flags=0): - """Emulate python-3.4 re.fullmatch().""" # noqa - return re.match("(?:" + regex + r")\Z", string, flags=flags) - from random import choice +from re import fullmatch -from builtins import object from django import forms from django.conf import settings from django.contrib.auth.models import User diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 78d7793a4..ed4ac89b1 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1,24 +1,20 @@ # -*- coding: utf-8 -*- """Project models.""" -from __future__ import ( - absolute_import, division, print_function, unicode_literals) - import fnmatch import logging import os -from builtins import object # pylint: disable=redefined-builtin -from six.moves import shlex_quote from django.conf import settings from django.contrib.auth.models import User -from django.urls import NoReverseMatch, reverse from django.db import models +from django.urls import NoReverseMatch, reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django_extensions.db.models import TimeStampedModel from future.backports.urllib.parse import urlparse # noqa from guardian.shortcuts import assign +from six.moves import shlex_quote from taggit.managers import TaggableManager from readthedocs.builds.constants import LATEST, STABLE @@ -27,15 +23,22 @@ from readthedocs.core.utils import broadcast, slugify from readthedocs.projects import constants from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.querysets import ( - ChildRelatedProjectQuerySet, FeatureQuerySet, ProjectQuerySet, - RelatedProjectQuerySet) + ChildRelatedProjectQuerySet, + FeatureQuerySet, + ProjectQuerySet, + RelatedProjectQuerySet, +) from readthedocs.projects.templatetags.projects_tags import sort_version_aware -from readthedocs.projects.validators import validate_domain_name, validate_repository_url +from readthedocs.projects.validators import ( + validate_domain_name, + validate_repository_url, +) from readthedocs.projects.version_handling import determine_stable_version from readthedocs.restapi.client import api from readthedocs.vcs_support.backends import backend_cls from readthedocs.vcs_support.utils import Lock, NonBlockingLock + log = logging.getLogger(__name__) diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index db7838bc8..ddf6033a5 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- """Project notifications""" -from __future__ import absolute_import from datetime import timedelta -from django.utils import timezone + from django.http import HttpRequest +from django.utils import timezone from messages_extends.models import Message + from readthedocs.notifications import Notification from readthedocs.notifications.constants import REQUIREMENT diff --git a/readthedocs/projects/signals.py b/readthedocs/projects/signals.py index 151259c4c..513150b11 100644 --- a/readthedocs/projects/signals.py +++ b/readthedocs/projects/signals.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Project signals""" -from __future__ import absolute_import import django.dispatch diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 0927bae21..b9af472d9 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -72,8 +72,8 @@ from .signals import ( after_vcs, before_build, before_vcs, - files_changed, domain_verify, + files_changed, ) diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index 5dc7b649d..f0c035891 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -1,12 +1,5 @@ """Project URLs for authenticated users.""" -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - from django.conf.urls import url from readthedocs.constants import pattern_opts @@ -19,8 +12,8 @@ from readthedocs.projects.views.private import ( DomainUpdate, EnvironmentVariableCreate, EnvironmentVariableDelete, - EnvironmentVariableList, EnvironmentVariableDetail, + EnvironmentVariableList, ImportView, IntegrationCreate, IntegrationDelete, @@ -34,6 +27,7 @@ from readthedocs.projects.views.private import ( ProjectUpdate, ) + urlpatterns = [ url(r'^$', ProjectDashboard.as_view(), diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 81998a7e8..87076f450 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -60,6 +60,7 @@ from readthedocs.projects.models import ( ) from readthedocs.projects.signals import project_import from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin + from ..tasks import retry_domain_verification From e363f6fe9fda5b6e7dddb9f17d4276376d7d0a40 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:15:51 +0100 Subject: [PATCH 52/65] Remove future leafovers --- readthedocs/projects/forms.py | 2 +- readthedocs/projects/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 4cc9f0ae4..af68c7b8f 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -4,6 +4,7 @@ from random import choice from re import fullmatch +from urllib.parse import urlparse from django import forms from django.conf import settings @@ -11,7 +12,6 @@ from django.contrib.auth.models import User from django.template.loader import render_to_string from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ -from future.backports.urllib.parse import urlparse from guardian.shortcuts import assign from textclassifier.validators import ClassifierValidator diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index ed4ac89b1..d3ae9b67a 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -4,6 +4,7 @@ import fnmatch import logging import os +from urllib.parse import urlparse from django.conf import settings from django.contrib.auth.models import User @@ -12,7 +13,6 @@ from django.urls import NoReverseMatch, reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django_extensions.db.models import TimeStampedModel -from future.backports.urllib.parse import urlparse # noqa from guardian.shortcuts import assign from six.moves import shlex_quote from taggit.managers import TaggableManager From 5798557a0d60635b925aa143370738804727cc83 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:16:25 +0100 Subject: [PATCH 53/65] isort over migration files Removes `from __future__ import ...` --- readthedocs/builds/migrations/0001_initial.py | 8 +++----- .../builds/migrations/0002_build_command_initial.py | 4 +--- readthedocs/builds/migrations/0003_add-cold-storage.py | 2 -- .../builds/migrations/0004_add-apiversion-proxy-model.py | 2 -- .../builds/migrations/0005_remove-version-alias.py | 3 +-- readthedocs/builds/migrations/0006_add_config_field.py | 4 +--- readthedocs/core/migrations/0001_initial.py | 5 +---- .../0002_make_userprofile_user_a_onetoonefield.py | 5 +---- readthedocs/core/migrations/0003_add_banned_status.py | 5 +---- readthedocs/core/migrations/0004_ad-opt-out.py | 4 +--- .../core/migrations/0005_migrate-old-passwords.py | 4 +--- readthedocs/gold/migrations/0001_initial.py | 5 +---- readthedocs/gold/migrations/0002_rename_last_4_digits.py | 2 -- .../0003_add_missing_model_change_migrations.py | 2 -- readthedocs/gold/migrations/0004_add_vat_id.py | 2 -- .../integrations/migrations/0001_add_http_exchange.py | 6 ++---- readthedocs/integrations/migrations/0002_add-webhook.py | 5 +---- .../0003_add_missing_model_change_migrations.py | 2 -- readthedocs/oauth/migrations/0001_initial.py | 5 +---- readthedocs/oauth/migrations/0002_combine_services.py | 7 ++----- readthedocs/oauth/migrations/0003_move_github.py | 8 +++----- .../migrations/0004_drop_github_and_bitbucket_models.py | 5 +---- .../oauth/migrations/0005_add_account_relation.py | 5 +---- readthedocs/oauth/migrations/0006_move_oauth_source.py | 5 +---- readthedocs/oauth/migrations/0007_org_slug_nonunique.py | 5 +---- .../oauth/migrations/0008_add-project-relation.py | 5 +---- .../0009_add_missing_model_change_migrations.py | 2 -- readthedocs/projects/migrations/0001_initial.py | 7 ++----- .../projects/migrations/0002_add_importedfile_model.py | 5 +---- .../projects/migrations/0003_project_cdn_enabled.py | 5 +---- .../migrations/0004_add_project_container_image.py | 5 +---- .../projects/migrations/0005_sync_project_model.py | 5 +---- .../projects/migrations/0006_add_domain_models.py | 5 +---- .../projects/migrations/0007_migrate_canonical_data.py | 6 +----- .../migrations/0008_add_subproject_alias_prefix.py | 5 +---- readthedocs/projects/migrations/0009_add_domain_field.py | 9 +++------ .../projects/migrations/0010_migrate_domain_data.py | 6 +++--- readthedocs/projects/migrations/0011_delete-url.py | 5 +---- .../migrations/0012_proper-name-for-install-project.py | 5 +---- .../projects/migrations/0013_add-container-limits.py | 5 +---- .../projects/migrations/0014_add-state-tracking.py | 5 +---- .../projects/migrations/0015_add_project_allow_promos.py | 5 +---- readthedocs/projects/migrations/0016_build-queue-name.py | 5 +---- readthedocs/projects/migrations/0017_add_domain_https.py | 5 +---- .../projects/migrations/0018_fix-translation-model.py | 5 +---- readthedocs/projects/migrations/0019_add-features.py | 2 -- .../projects/migrations/0020_add-api-project-proxy.py | 2 -- .../migrations/0021_add-webhook-deprecation-feature.py | 2 -- readthedocs/projects/migrations/0022_add-alias-slug.py | 2 -- .../projects/migrations/0023_migrate-alias-slug.py | 4 +--- .../projects/migrations/0024_add-show-version-warning.py | 3 +-- .../0025_show-version-warning-existing-projects.py | 2 -- readthedocs/projects/migrations/0026_ad-free-option.py | 2 -- .../migrations/0027_remove_json_with_html_feature.py | 2 -- .../0028_remove_comments_and_update_old_migration.py | 2 -- .../projects/migrations/0029_add_additional_languages.py | 2 -- .../migrations/0030_change-max-length-project-slug.py | 2 -- .../migrations/0031_add_modified_date_importedfile.py | 2 -- .../projects/migrations/0032_increase_webhook_maxsize.py | 2 -- .../migrations/0033_add_environment_variables.py | 4 +--- .../0034_remove_unused_project_model_fields.py | 2 -- .../migrations/0035_container_time_limit_as_integer.py | 2 -- .../projects/migrations/0036_remove-auto-doctype.py | 2 -- readthedocs/redirects/migrations/0001_initial.py | 5 +---- .../0002_add_missing_model_change_migrations.py | 2 -- 65 files changed, 53 insertions(+), 210 deletions(-) diff --git a/readthedocs/builds/migrations/0001_initial.py b/readthedocs/builds/migrations/0001_initial.py index 73ff3a648..27f61efb7 100644 --- a/readthedocs/builds/migrations/0001_initial.py +++ b/readthedocs/builds/migrations/0001_initial.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations -import readthedocs.builds.version_slug import taggit.managers +from django.db import migrations, models + +import readthedocs.builds.version_slug class Migration(migrations.Migration): diff --git a/readthedocs/builds/migrations/0002_build_command_initial.py b/readthedocs/builds/migrations/0002_build_command_initial.py index 7b45f9468..d78b9d13d 100644 --- a/readthedocs/builds/migrations/0002_build_command_initial.py +++ b/readthedocs/builds/migrations/0002_build_command_initial.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +from django.db import migrations, models -from __future__ import absolute_import -from django.db import models, migrations import readthedocs.builds.models diff --git a/readthedocs/builds/migrations/0003_add-cold-storage.py b/readthedocs/builds/migrations/0003_add-cold-storage.py index 7c474a973..2c53cc144 100644 --- a/readthedocs/builds/migrations/0003_add-cold-storage.py +++ b/readthedocs/builds/migrations/0003_add-cold-storage.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-10-09 20:14 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/builds/migrations/0004_add-apiversion-proxy-model.py b/readthedocs/builds/migrations/0004_add-apiversion-proxy-model.py index b96db28e9..8247d9a02 100644 --- a/readthedocs/builds/migrations/0004_add-apiversion-proxy-model.py +++ b/readthedocs/builds/migrations/0004_add-apiversion-proxy-model.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-10-27 00:17 -from __future__ import unicode_literals - from django.db import migrations diff --git a/readthedocs/builds/migrations/0005_remove-version-alias.py b/readthedocs/builds/migrations/0005_remove-version-alias.py index a41af51e2..65f6aadd3 100644 --- a/readthedocs/builds/migrations/0005_remove-version-alias.py +++ b/readthedocs/builds/migrations/0005_remove-version-alias.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-10-17 04:20 -from __future__ import unicode_literals - from django.db import migrations, models + import readthedocs.builds.version_slug diff --git a/readthedocs/builds/migrations/0006_add_config_field.py b/readthedocs/builds/migrations/0006_add_config_field.py index 7af36e8ad..deb370027 100644 --- a/readthedocs/builds/migrations/0006_add_config_field.py +++ b/readthedocs/builds/migrations/0006_add_config_field.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-02 13:24 -from __future__ import unicode_literals - -from django.db import migrations import jsonfield.fields +from django.db import migrations class Migration(migrations.Migration): diff --git a/readthedocs/core/migrations/0001_initial.py b/readthedocs/core/migrations/0001_initial.py index e3d5b9484..bb5bde042 100644 --- a/readthedocs/core/migrations/0001_initial.py +++ b/readthedocs/core/migrations/0001_initial.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/core/migrations/0002_make_userprofile_user_a_onetoonefield.py b/readthedocs/core/migrations/0002_make_userprofile_user_a_onetoonefield.py index f5e6255cc..5f7d04ff9 100644 --- a/readthedocs/core/migrations/0002_make_userprofile_user_a_onetoonefield.py +++ b/readthedocs/core/migrations/0002_make_userprofile_user_a_onetoonefield.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/core/migrations/0003_add_banned_status.py b/readthedocs/core/migrations/0003_add_banned_status.py index f3dfbb8b7..95d26eefc 100644 --- a/readthedocs/core/migrations/0003_add_banned_status.py +++ b/readthedocs/core/migrations/0003_add_banned_status.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/core/migrations/0004_ad-opt-out.py b/readthedocs/core/migrations/0004_ad-opt-out.py index 9e8c5bf32..f5d6ae3d0 100644 --- a/readthedocs/core/migrations/0004_ad-opt-out.py +++ b/readthedocs/core/migrations/0004_ad-opt-out.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-06-14 18:06 -from __future__ import unicode_literals - import annoying.fields +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/readthedocs/core/migrations/0005_migrate-old-passwords.py b/readthedocs/core/migrations/0005_migrate-old-passwords.py index 2ef614d0d..8a44107c9 100644 --- a/readthedocs/core/migrations/0005_migrate-old-passwords.py +++ b/readthedocs/core/migrations/0005_migrate-old-passwords.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-11 17:28 -from __future__ import unicode_literals - -from django.db import migrations from django.contrib.auth.hashers import make_password +from django.db import migrations def forwards_func(apps, schema_editor): diff --git a/readthedocs/gold/migrations/0001_initial.py b/readthedocs/gold/migrations/0001_initial.py index baeb6d283..80a61461d 100644 --- a/readthedocs/gold/migrations/0001_initial.py +++ b/readthedocs/gold/migrations/0001_initial.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/gold/migrations/0002_rename_last_4_digits.py b/readthedocs/gold/migrations/0002_rename_last_4_digits.py index 2ed345fd3..fc0aad453 100644 --- a/readthedocs/gold/migrations/0002_rename_last_4_digits.py +++ b/readthedocs/gold/migrations/0002_rename_last_4_digits.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-07-16 15:45 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/gold/migrations/0003_add_missing_model_change_migrations.py b/readthedocs/gold/migrations/0003_add_missing_model_change_migrations.py index 2e919ac20..f1f9f1dbd 100644 --- a/readthedocs/gold/migrations/0003_add_missing_model_change_migrations.py +++ b/readthedocs/gold/migrations/0003_add_missing_model_change_migrations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-31 11:25 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/gold/migrations/0004_add_vat_id.py b/readthedocs/gold/migrations/0004_add_vat_id.py index eab1771f1..ee4899eeb 100644 --- a/readthedocs/gold/migrations/0004_add_vat_id.py +++ b/readthedocs/gold/migrations/0004_add_vat_id.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-10-22 07:13 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/integrations/migrations/0001_add_http_exchange.py b/readthedocs/integrations/migrations/0001_add_http_exchange.py index b4440b6ea..c1ee6c0b7 100644 --- a/readthedocs/integrations/migrations/0001_add_http_exchange.py +++ b/readthedocs/integrations/migrations/0001_add_http_exchange.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-03-16 18:30 -from __future__ import unicode_literals +import uuid -from __future__ import absolute_import -from django.db import migrations, models import django.db.models.deletion import jsonfield.fields -import uuid +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/integrations/migrations/0002_add-webhook.py b/readthedocs/integrations/migrations/0002_add-webhook.py index 3d061993c..e42ef2a86 100644 --- a/readthedocs/integrations/migrations/0002_add-webhook.py +++ b/readthedocs/integrations/migrations/0002_add-webhook.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-03-29 21:29 -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import migrations, models import django.db.models.deletion import jsonfield.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/integrations/migrations/0003_add_missing_model_change_migrations.py b/readthedocs/integrations/migrations/0003_add_missing_model_change_migrations.py index a1356c48f..d1a431441 100644 --- a/readthedocs/integrations/migrations/0003_add_missing_model_change_migrations.py +++ b/readthedocs/integrations/migrations/0003_add_missing_model_change_migrations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-31 11:25 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/oauth/migrations/0001_initial.py b/readthedocs/oauth/migrations/0001_initial.py index 352af573c..3b3c4b154 100644 --- a/readthedocs/oauth/migrations/0001_initial.py +++ b/readthedocs/oauth/migrations/0001_initial.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/oauth/migrations/0002_combine_services.py b/readthedocs/oauth/migrations/0002_combine_services.py index ab053be97..1290dc5d9 100644 --- a/readthedocs/oauth/migrations/0002_combine_services.py +++ b/readthedocs/oauth/migrations/0002_combine_services.py @@ -1,10 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations -from django.conf import settings import django.core.validators +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/oauth/migrations/0003_move_github.py b/readthedocs/oauth/migrations/0003_move_github.py index 7ff1dbc8e..14998f5e9 100644 --- a/readthedocs/oauth/migrations/0003_move_github.py +++ b/readthedocs/oauth/migrations/0003_move_github.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -import json import gc +import json import logging -from django.db import models, migrations +from django.db import migrations, models + log = logging.getLogger(__name__) diff --git a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py index 628891ff7..17bfef298 100644 --- a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py +++ b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models def forwards_remove_content_types(apps, schema_editor): diff --git a/readthedocs/oauth/migrations/0005_add_account_relation.py b/readthedocs/oauth/migrations/0005_add_account_relation.py index 100bcd71a..c8c466db3 100644 --- a/readthedocs/oauth/migrations/0005_add_account_relation.py +++ b/readthedocs/oauth/migrations/0005_add_account_relation.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/oauth/migrations/0006_move_oauth_source.py b/readthedocs/oauth/migrations/0006_move_oauth_source.py index a19d0be04..66d0db28f 100644 --- a/readthedocs/oauth/migrations/0006_move_oauth_source.py +++ b/readthedocs/oauth/migrations/0006_move_oauth_source.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models def forwards_move_repo_source(apps, schema_editor): diff --git a/readthedocs/oauth/migrations/0007_org_slug_nonunique.py b/readthedocs/oauth/migrations/0007_org_slug_nonunique.py index 65f6f4f4f..970780384 100644 --- a/readthedocs/oauth/migrations/0007_org_slug_nonunique.py +++ b/readthedocs/oauth/migrations/0007_org_slug_nonunique.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/oauth/migrations/0008_add-project-relation.py b/readthedocs/oauth/migrations/0008_add-project-relation.py index 1e2a478e6..070b57e65 100644 --- a/readthedocs/oauth/migrations/0008_add-project-relation.py +++ b/readthedocs/oauth/migrations/0008_add-project-relation.py @@ -1,10 +1,7 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-03-22 20:10 -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/oauth/migrations/0009_add_missing_model_change_migrations.py b/readthedocs/oauth/migrations/0009_add_missing_model_change_migrations.py index 015c233ac..c23743a84 100644 --- a/readthedocs/oauth/migrations/0009_add_missing_model_change_migrations.py +++ b/readthedocs/oauth/migrations/0009_add_missing_model_change_migrations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-31 11:25 -from __future__ import unicode_literals - import django.core.validators from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0001_initial.py b/readthedocs/projects/migrations/0001_initial.py index 734358ad0..00d2a7915 100644 --- a/readthedocs/projects/migrations/0001_initial.py +++ b/readthedocs/projects/migrations/0001_initial.py @@ -1,10 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations -from django.conf import settings import taggit.managers +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0002_add_importedfile_model.py b/readthedocs/projects/migrations/0002_add_importedfile_model.py index a03fff529..cfa6f3b9e 100644 --- a/readthedocs/projects/migrations/0002_add_importedfile_model.py +++ b/readthedocs/projects/migrations/0002_add_importedfile_model.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0003_project_cdn_enabled.py b/readthedocs/projects/migrations/0003_project_cdn_enabled.py index 471df3319..e89cfed99 100644 --- a/readthedocs/projects/migrations/0003_project_cdn_enabled.py +++ b/readthedocs/projects/migrations/0003_project_cdn_enabled.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0004_add_project_container_image.py b/readthedocs/projects/migrations/0004_add_project_container_image.py index 70c969d1b..724e62e45 100644 --- a/readthedocs/projects/migrations/0004_add_project_container_image.py +++ b/readthedocs/projects/migrations/0004_add_project_container_image.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0005_sync_project_model.py b/readthedocs/projects/migrations/0005_sync_project_model.py index 75b9e6d5e..12537572c 100644 --- a/readthedocs/projects/migrations/0005_sync_project_model.py +++ b/readthedocs/projects/migrations/0005_sync_project_model.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0006_add_domain_models.py b/readthedocs/projects/migrations/0006_add_domain_models.py index 78e05b81e..e50617a69 100644 --- a/readthedocs/projects/migrations/0006_add_domain_models.py +++ b/readthedocs/projects/migrations/0006_add_domain_models.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0007_migrate_canonical_data.py b/readthedocs/projects/migrations/0007_migrate_canonical_data.py index 8c842a6a0..79647aa8f 100644 --- a/readthedocs/projects/migrations/0007_migrate_canonical_data.py +++ b/readthedocs/projects/migrations/0007_migrate_canonical_data.py @@ -1,9 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function - -from __future__ import absolute_import -from django.db import migrations -from django.db import transaction +from django.db import migrations, transaction def migrate_canonical(apps, schema_editor): diff --git a/readthedocs/projects/migrations/0008_add_subproject_alias_prefix.py b/readthedocs/projects/migrations/0008_add_subproject_alias_prefix.py index b3eb93388..ec61ecabd 100644 --- a/readthedocs/projects/migrations/0008_add_subproject_alias_prefix.py +++ b/readthedocs/projects/migrations/0008_add_subproject_alias_prefix.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0009_add_domain_field.py b/readthedocs/projects/migrations/0009_add_domain_field.py index 20230a473..20703e89d 100644 --- a/readthedocs/projects/migrations/0009_add_domain_field.py +++ b/readthedocs/projects/migrations/0009_add_domain_field.py @@ -1,12 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations -import django.contrib.sites.models - import uuid +import django.contrib.sites.models +from django.db import migrations, models + class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py index 2d18756d6..301442333 100644 --- a/readthedocs/projects/migrations/0010_migrate_domain_data.py +++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import (absolute_import, print_function, unicode_literals) - -from django.db import models, migrations from urllib.parse import urlparse + +from django.db import migrations, models + import readthedocs.projects.validators diff --git a/readthedocs/projects/migrations/0011_delete-url.py b/readthedocs/projects/migrations/0011_delete-url.py index fcd83c027..caa9c612d 100644 --- a/readthedocs/projects/migrations/0011_delete-url.py +++ b/readthedocs/projects/migrations/0011_delete-url.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0012_proper-name-for-install-project.py b/readthedocs/projects/migrations/0012_proper-name-for-install-project.py index 8f5f11626..9f143977c 100644 --- a/readthedocs/projects/migrations/0012_proper-name-for-install-project.py +++ b/readthedocs/projects/migrations/0012_proper-name-for-install-project.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0013_add-container-limits.py b/readthedocs/projects/migrations/0013_add-container-limits.py index c28206090..9a052e00f 100644 --- a/readthedocs/projects/migrations/0013_add-container-limits.py +++ b/readthedocs/projects/migrations/0013_add-container-limits.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0014_add-state-tracking.py b/readthedocs/projects/migrations/0014_add-state-tracking.py index 628bf970d..d2c34c28e 100644 --- a/readthedocs/projects/migrations/0014_add-state-tracking.py +++ b/readthedocs/projects/migrations/0014_add-state-tracking.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0015_add_project_allow_promos.py b/readthedocs/projects/migrations/0015_add_project_allow_promos.py index 5c50eeac9..882893160 100644 --- a/readthedocs/projects/migrations/0015_add_project_allow_promos.py +++ b/readthedocs/projects/migrations/0015_add_project_allow_promos.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0016_build-queue-name.py b/readthedocs/projects/migrations/0016_build-queue-name.py index d4e2201b2..6baa0a922 100644 --- a/readthedocs/projects/migrations/0016_build-queue-name.py +++ b/readthedocs/projects/migrations/0016_build-queue-name.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models def update_build_queue(apps, schema): diff --git a/readthedocs/projects/migrations/0017_add_domain_https.py b/readthedocs/projects/migrations/0017_add_domain_https.py index 9bf94eeb5..18788581c 100644 --- a/readthedocs/projects/migrations/0017_add_domain_https.py +++ b/readthedocs/projects/migrations/0017_add_domain_https.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0018_fix-translation-model.py b/readthedocs/projects/migrations/0018_fix-translation-model.py index bfe283d27..2541fb0d3 100644 --- a/readthedocs/projects/migrations/0018_fix-translation-model.py +++ b/readthedocs/projects/migrations/0018_fix-translation-model.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0019_add-features.py b/readthedocs/projects/migrations/0019_add-features.py index 6d1036dd1..6b7ee7a8b 100644 --- a/readthedocs/projects/migrations/0019_add-features.py +++ b/readthedocs/projects/migrations/0019_add-features.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-10-27 12:55 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0020_add-api-project-proxy.py b/readthedocs/projects/migrations/0020_add-api-project-proxy.py index 34eafa484..9e1c8458b 100644 --- a/readthedocs/projects/migrations/0020_add-api-project-proxy.py +++ b/readthedocs/projects/migrations/0020_add-api-project-proxy.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-10-27 12:56 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py index 84dc7cf92..a3572c84c 100644 --- a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py +++ b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py @@ -2,8 +2,6 @@ """Add feature for allowing access to deprecated webhook endpoints""" -from __future__ import unicode_literals - from django.db import migrations diff --git a/readthedocs/projects/migrations/0022_add-alias-slug.py b/readthedocs/projects/migrations/0022_add-alias-slug.py index 8439c56e8..90c434a27 100644 --- a/readthedocs/projects/migrations/0022_add-alias-slug.py +++ b/readthedocs/projects/migrations/0022_add-alias-slug.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-12-21 16:30 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0023_migrate-alias-slug.py b/readthedocs/projects/migrations/0023_migrate-alias-slug.py index 4942848b9..c86ea6e07 100644 --- a/readthedocs/projects/migrations/0023_migrate-alias-slug.py +++ b/readthedocs/projects/migrations/0023_migrate-alias-slug.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-12-21 16:31 -from __future__ import unicode_literals +import re from django.db import migrations -import re - class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0024_add-show-version-warning.py b/readthedocs/projects/migrations/0024_add-show-version-warning.py index 6bc60e4ae..bfa0b2edb 100644 --- a/readthedocs/projects/migrations/0024_add-show-version-warning.py +++ b/readthedocs/projects/migrations/0024_add-show-version-warning.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-05-02 01:27 -from __future__ import unicode_literals - from django.db import migrations, models + import readthedocs.projects.validators diff --git a/readthedocs/projects/migrations/0025_show-version-warning-existing-projects.py b/readthedocs/projects/migrations/0025_show-version-warning-existing-projects.py index 5d073258c..e38349fc5 100644 --- a/readthedocs/projects/migrations/0025_show-version-warning-existing-projects.py +++ b/readthedocs/projects/migrations/0025_show-version-warning-existing-projects.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-05-07 19:25 -from __future__ import unicode_literals - from django.db import migrations diff --git a/readthedocs/projects/migrations/0026_ad-free-option.py b/readthedocs/projects/migrations/0026_ad-free-option.py index a32fe5e74..d108f25e1 100644 --- a/readthedocs/projects/migrations/0026_ad-free-option.py +++ b/readthedocs/projects/migrations/0026_ad-free-option.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-06-29 15:53 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0027_remove_json_with_html_feature.py b/readthedocs/projects/migrations/0027_remove_json_with_html_feature.py index c5daf0ece..d0fb5d14e 100644 --- a/readthedocs/projects/migrations/0027_remove_json_with_html_feature.py +++ b/readthedocs/projects/migrations/0027_remove_json_with_html_feature.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-08-22 09:19 -from __future__ import unicode_literals - from django.db import migrations diff --git a/readthedocs/projects/migrations/0028_remove_comments_and_update_old_migration.py b/readthedocs/projects/migrations/0028_remove_comments_and_update_old_migration.py index 056e9b8e6..4d4fc04a5 100644 --- a/readthedocs/projects/migrations/0028_remove_comments_and_update_old_migration.py +++ b/readthedocs/projects/migrations/0028_remove_comments_and_update_old_migration.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-31 10:08 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0029_add_additional_languages.py b/readthedocs/projects/migrations/0029_add_additional_languages.py index b4cfc7753..8e9e48d4b 100644 --- a/readthedocs/projects/migrations/0029_add_additional_languages.py +++ b/readthedocs/projects/migrations/0029_add_additional_languages.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-01 13:38 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0030_change-max-length-project-slug.py b/readthedocs/projects/migrations/0030_change-max-length-project-slug.py index 7e9b48da2..ee27e9602 100644 --- a/readthedocs/projects/migrations/0030_change-max-length-project-slug.py +++ b/readthedocs/projects/migrations/0030_change-max-length-project-slug.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-01 20:55 -from __future__ import unicode_literals - from django.db import migrations, models from django.db.models.functions import Length diff --git a/readthedocs/projects/migrations/0031_add_modified_date_importedfile.py b/readthedocs/projects/migrations/0031_add_modified_date_importedfile.py index 255da1c00..617a420c2 100644 --- a/readthedocs/projects/migrations/0031_add_modified_date_importedfile.py +++ b/readthedocs/projects/migrations/0031_add_modified_date_importedfile.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-01 14:37 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0032_increase_webhook_maxsize.py b/readthedocs/projects/migrations/0032_increase_webhook_maxsize.py index eed6d3de0..49b231590 100644 --- a/readthedocs/projects/migrations/0032_increase_webhook_maxsize.py +++ b/readthedocs/projects/migrations/0032_increase_webhook_maxsize.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-06 23:12 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0033_add_environment_variables.py b/readthedocs/projects/migrations/0033_add_environment_variables.py index de9e3d18e..9279fa8b3 100644 --- a/readthedocs/projects/migrations/0033_add_environment_variables.py +++ b/readthedocs/projects/migrations/0033_add_environment_variables.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-12 13:57 -from __future__ import unicode_literals - -from django.db import migrations, models import django.db.models.deletion import django_extensions.db.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0034_remove_unused_project_model_fields.py b/readthedocs/projects/migrations/0034_remove_unused_project_model_fields.py index 996cdd9c6..4ad8b27f8 100644 --- a/readthedocs/projects/migrations/0034_remove_unused_project_model_fields.py +++ b/readthedocs/projects/migrations/0034_remove_unused_project_model_fields.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-05 12:20 -from __future__ import unicode_literals - from django.db import migrations diff --git a/readthedocs/projects/migrations/0035_container_time_limit_as_integer.py b/readthedocs/projects/migrations/0035_container_time_limit_as_integer.py index 28dab1240..7256548fd 100644 --- a/readthedocs/projects/migrations/0035_container_time_limit_as_integer.py +++ b/readthedocs/projects/migrations/0035_container_time_limit_as_integer.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-12-10 11:19 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0036_remove-auto-doctype.py b/readthedocs/projects/migrations/0036_remove-auto-doctype.py index 8688fc4d0..b0f9ad281 100644 --- a/readthedocs/projects/migrations/0036_remove-auto-doctype.py +++ b/readthedocs/projects/migrations/0036_remove-auto-doctype.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-12-17 17:32 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/readthedocs/redirects/migrations/0001_initial.py b/readthedocs/redirects/migrations/0001_initial.py index 010f36342..0bb2fb946 100644 --- a/readthedocs/redirects/migrations/0001_initial.py +++ b/readthedocs/redirects/migrations/0001_initial.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/readthedocs/redirects/migrations/0002_add_missing_model_change_migrations.py b/readthedocs/redirects/migrations/0002_add_missing_model_change_migrations.py index a837e6fb1..e1d83010c 100644 --- a/readthedocs/redirects/migrations/0002_add_missing_model_change_migrations.py +++ b/readthedocs/redirects/migrations/0002_add_missing_model_change_migrations.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-10-31 11:25 -from __future__ import unicode_literals - from django.db import migrations, models From 55e1a2f37eebaef8a4a47384e36a39cb6b3b1541 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:19:04 +0100 Subject: [PATCH 54/65] isort on all tests files --- readthedocs/analytics/tests.py | 1 + readthedocs/config/tests/test_config.py | 3 +-- readthedocs/config/tests/test_find.py | 1 + readthedocs/config/tests/test_parser.py | 1 + readthedocs/config/tests/test_utils.py | 1 + readthedocs/config/tests/test_validation.py | 1 + readthedocs/core/tests/test_signals.py | 1 + readthedocs/gold/tests/test_forms.py | 1 + readthedocs/gold/tests/test_signals.py | 1 + readthedocs/rtd_tests/base.py | 1 + readthedocs/rtd_tests/files/conf.py | 3 +-- readthedocs/rtd_tests/mocks/environment.py | 1 + readthedocs/rtd_tests/mocks/mock_api.py | 1 + readthedocs/rtd_tests/mocks/paths.py | 1 + .../tests/projects/test_admin_actions.py | 1 + readthedocs/rtd_tests/tests/test_api.py | 1 + .../rtd_tests/tests/test_api_permissions.py | 1 + .../tests/test_api_version_compare.py | 1 + readthedocs/rtd_tests/tests/test_backend.py | 1 + .../rtd_tests/tests/test_backend_svn.py | 1 + .../rtd_tests/tests/test_build_config.py | 1 + .../rtd_tests/tests/test_build_forms.py | 1 + .../tests/test_build_notifications.py | 1 + readthedocs/rtd_tests/tests/test_builds.py | 1 + readthedocs/rtd_tests/tests/test_celery.py | 20 +++++++++---------- .../tests/test_config_integration.py | 1 + readthedocs/rtd_tests/tests/test_core_tags.py | 1 + .../rtd_tests/tests/test_core_utils.py | 1 + .../rtd_tests/tests/test_doc_builder.py | 1 + .../rtd_tests/tests/test_doc_building.py | 1 + .../rtd_tests/tests/test_doc_serving.py | 15 ++++++-------- readthedocs/rtd_tests/tests/test_domains.py | 1 + readthedocs/rtd_tests/tests/test_extend.py | 1 + readthedocs/rtd_tests/tests/test_footer.py | 1 + readthedocs/rtd_tests/tests/test_gold.py | 1 + .../rtd_tests/tests/test_imported_file.py | 1 + .../rtd_tests/tests/test_integrations.py | 1 + .../rtd_tests/tests/test_middleware.py | 1 + .../rtd_tests/tests/test_notifications.py | 16 +++++++++------ readthedocs/rtd_tests/tests/test_oauth.py | 1 + .../rtd_tests/tests/test_post_commit_hooks.py | 3 ++- readthedocs/rtd_tests/tests/test_privacy.py | 1 + .../rtd_tests/tests/test_privacy_urls.py | 9 +++------ .../rtd_tests/tests/test_profile_views.py | 1 + readthedocs/rtd_tests/tests/test_project.py | 1 + .../rtd_tests/tests/test_project_forms.py | 3 ++- .../rtd_tests/tests/test_project_querysets.py | 10 ++++++---- .../rtd_tests/tests/test_project_symlinks.py | 1 + .../rtd_tests/tests/test_project_views.py | 1 + readthedocs/rtd_tests/tests/test_redirects.py | 1 + .../rtd_tests/tests/test_repo_parsing.py | 1 + readthedocs/rtd_tests/tests/test_resolver.py | 1 + .../rtd_tests/tests/test_restapi_client.py | 1 + .../tests/test_search_json_parsing.py | 1 + .../rtd_tests/tests/test_single_version.py | 1 + .../rtd_tests/tests/test_subprojects.py | 1 + .../rtd_tests/tests/test_sync_versions.py | 1 + readthedocs/rtd_tests/tests/test_urls.py | 1 + .../tests/test_version_commit_name.py | 1 + .../rtd_tests/tests/test_version_config.py | 1 + .../rtd_tests/tests/test_version_slug.py | 1 + readthedocs/rtd_tests/tests/test_views.py | 4 +++- readthedocs/rtd_tests/utils.py | 1 + readthedocs/search/tests/conftest.py | 1 + readthedocs/search/tests/dummy_data.py | 1 + readthedocs/search/tests/test_views.py | 1 + readthedocs/search/tests/utils.py | 1 + readthedocs/vcs_support/tests.py | 1 + 68 files changed, 102 insertions(+), 42 deletions(-) diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 934355516..3a18165eb 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -2,6 +2,7 @@ from django.test import TestCase from .utils import anonymize_ip_address, anonymize_user_agent + class UtilsTests(TestCase): def test_anonymize_ip(self): self.assertEqual(anonymize_ip_address('127.0.0.1'), '127.0.0.0') diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 0d223a671..09b57adec 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - import os import re import textwrap @@ -38,6 +36,7 @@ from readthedocs.config.validation import ( from .utils import apply_fs + yaml_config_dir = { 'readthedocs.yml': textwrap.dedent( ''' diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index 4ebcb783c..b982245fb 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -4,6 +4,7 @@ from readthedocs.config.find import find_one from .utils import apply_fs + def test_find_no_files(tmpdir): with tmpdir.as_cwd(): path = find_one(os.getcwd(), r'readthedocs.yml') diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py index 37476af91..d140c8c0c 100644 --- a/readthedocs/config/tests/test_parser.py +++ b/readthedocs/config/tests/test_parser.py @@ -4,6 +4,7 @@ from pytest import raises from readthedocs.config.parser import ParseError, parse + def test_parse_empty_config_file(): buf = StringIO('') with raises(ParseError): diff --git a/readthedocs/config/tests/test_utils.py b/readthedocs/config/tests/test_utils.py index 2332b3071..04f019276 100644 --- a/readthedocs/config/tests/test_utils.py +++ b/readthedocs/config/tests/test_utils.py @@ -1,5 +1,6 @@ from .utils import apply_fs + def test_apply_fs_with_empty_contents(tmpdir): # Doesn't do anything if second parameter is empty. apply_fs(tmpdir, {}) diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py index fd7d9e781..780883e00 100644 --- a/readthedocs/config/tests/test_validation.py +++ b/readthedocs/config/tests/test_validation.py @@ -22,6 +22,7 @@ from readthedocs.config.validation import ( validate_string, ) + class TestValidateBool: def test_it_accepts_true(self): assert validate_bool(True) is True diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py index f96bb30d5..b6fa0d8ad 100644 --- a/readthedocs/core/tests/test_signals.py +++ b/readthedocs/core/tests/test_signals.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import User from readthedocs.oauth.models import RemoteOrganization from readthedocs.projects.models import Project + @pytest.mark.django_db class TestProjectOrganizationSignal: diff --git a/readthedocs/gold/tests/test_forms.py b/readthedocs/gold/tests/test_forms.py index 5ffaf9183..074102336 100644 --- a/readthedocs/gold/tests/test_forms.py +++ b/readthedocs/gold/tests/test_forms.py @@ -8,6 +8,7 @@ from readthedocs.projects.models import Project from ..forms import GoldSubscriptionForm from ..models import GoldUser + class GoldSubscriptionFormTests(TestCase): def setUp(self): diff --git a/readthedocs/gold/tests/test_signals.py b/readthedocs/gold/tests/test_signals.py index 947f042e0..55103d155 100644 --- a/readthedocs/gold/tests/test_signals.py +++ b/readthedocs/gold/tests/test_signals.py @@ -9,6 +9,7 @@ from readthedocs.projects.models import Project from ..models import GoldUser from ..signals import delete_customer + class GoldSignalTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py index b5ab43115..270fe4828 100644 --- a/readthedocs/rtd_tests/base.py +++ b/readthedocs/rtd_tests/base.py @@ -12,6 +12,7 @@ from django.contrib.sessions.middleware import SessionMiddleware from django.test import RequestFactory, TestCase from mock import patch + log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 11f872849..972ec96be 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - from datetime import datetime from recommonmark.parser import CommonMarkParser + extensions = [] templates_path = ['templates', '_templates', '.templates'] source_suffix = ['.rst', '.md'] diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py index a5aa1b8fe..0d3ef1a56 100644 --- a/readthedocs/rtd_tests/mocks/environment.py +++ b/readthedocs/rtd_tests/mocks/environment.py @@ -1,6 +1,7 @@ # pylint: disable=missing-docstring import mock + class EnvironmentMockGroup: """Mock out necessary environment pieces""" diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py index 3dd54d663..eeff8aadb 100644 --- a/readthedocs/rtd_tests/mocks/mock_api.py +++ b/readthedocs/rtd_tests/mocks/mock_api.py @@ -4,6 +4,7 @@ from contextlib import contextmanager import mock + # Mock tastypi API. diff --git a/readthedocs/rtd_tests/mocks/paths.py b/readthedocs/rtd_tests/mocks/paths.py index ee8b29b7a..3e4539ebc 100644 --- a/readthedocs/rtd_tests/mocks/paths.py +++ b/readthedocs/rtd_tests/mocks/paths.py @@ -4,6 +4,7 @@ import re import mock + def fake_paths(check): """ Usage: diff --git a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py index 5c856ebad..c33f90ac9 100644 --- a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py +++ b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py @@ -8,6 +8,7 @@ from django.test import TestCase from readthedocs.core.models import UserProfile from readthedocs.projects.models import Project + class ProjectAdminActionsTest(TestCase): @classmethod diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index 01693da18..03d8f5f13 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -34,6 +34,7 @@ from readthedocs.restapi.views.integrations import ( ) from readthedocs.restapi.views.task_views import get_status_data + super_auth = base64.b64encode(b'super:test').decode('utf-8') eric_auth = base64.b64encode(b'eric:test').decode('utf-8') diff --git a/readthedocs/rtd_tests/tests/test_api_permissions.py b/readthedocs/rtd_tests/tests/test_api_permissions.py index 40d348c6c..d887be4f5 100644 --- a/readthedocs/rtd_tests/tests/test_api_permissions.py +++ b/readthedocs/rtd_tests/tests/test_api_permissions.py @@ -5,6 +5,7 @@ from mock import Mock from readthedocs.restapi.permissions import APIRestrictedPermission + class APIRestrictedPermissionTests(TestCase): def get_request(self, method, is_admin): request = Mock() diff --git a/readthedocs/rtd_tests/tests/test_api_version_compare.py b/readthedocs/rtd_tests/tests/test_api_version_compare.py index df4df452c..509cbcc76 100644 --- a/readthedocs/rtd_tests/tests/test_api_version_compare.py +++ b/readthedocs/rtd_tests/tests/test_api_version_compare.py @@ -4,6 +4,7 @@ from readthedocs.builds.constants import LATEST from readthedocs.projects.models import Project from readthedocs.restapi.views.footer_views import get_version_compare_data + class VersionCompareTests(TestCase): fixtures = ['eric.json', 'test_data.json'] diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index 20320b29d..500646797 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -21,6 +21,7 @@ from readthedocs.rtd_tests.utils import ( make_test_hg, ) + class TestGitBackend(RTDTestCase): def setUp(self): git_repo = make_test_git() diff --git a/readthedocs/rtd_tests/tests/test_backend_svn.py b/readthedocs/rtd_tests/tests/test_backend_svn.py index e3cf9e2e2..bd6f45511 100644 --- a/readthedocs/rtd_tests/tests/test_backend_svn.py +++ b/readthedocs/rtd_tests/tests/test_backend_svn.py @@ -9,6 +9,7 @@ from readthedocs.projects.models import Project from readthedocs.rtd_tests.base import RTDTestCase from readthedocs.vcs_support.backends.svn import Backend as SvnBackend + class TestSvnBackend(RTDTestCase): def test_get_url(self): diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py index 90a28ce25..b3ffbb7cd 100644 --- a/readthedocs/rtd_tests/tests/test_build_config.py +++ b/readthedocs/rtd_tests/tests/test_build_config.py @@ -6,6 +6,7 @@ from yamale.validators import DefaultValidators, Validator from readthedocs.config.tests import utils + V2_SCHEMA = path.join( path.dirname(__file__), '../fixtures/spec/v2/schema.yml' diff --git a/readthedocs/rtd_tests/tests/test_build_forms.py b/readthedocs/rtd_tests/tests/test_build_forms.py index d69ca2894..f85ae7419 100644 --- a/readthedocs/rtd_tests/tests/test_build_forms.py +++ b/readthedocs/rtd_tests/tests/test_build_forms.py @@ -8,6 +8,7 @@ from readthedocs.builds.models import Version from readthedocs.projects.constants import PRIVATE from readthedocs.projects.models import Project + class TestVersionForm(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_build_notifications.py b/readthedocs/rtd_tests/tests/test_build_notifications.py index b276d1d93..4964f32c7 100644 --- a/readthedocs/rtd_tests/tests/test_build_notifications.py +++ b/readthedocs/rtd_tests/tests/test_build_notifications.py @@ -11,6 +11,7 @@ from readthedocs.projects.forms import WebHookForm from readthedocs.projects.models import EmailHook, Project, WebHook from readthedocs.projects.tasks import send_notifications + class BuildNotificationsTests(TestCase): def setUp(self): self.project = fixture.get(Project) diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 6f8808bc1..2b117fa4f 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -14,6 +14,7 @@ from readthedocs.rtd_tests.tests.test_config_integration import create_load from ..mocks.environment import EnvironmentMockGroup + class BuildEnvironmentTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index ccd2b70e3..f7f16d0ae 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals - import os import shutil from os.path import exists @@ -8,20 +6,22 @@ from tempfile import mkdtemp from django.contrib.auth.models import User from django_dynamic_fixture import get -from mock import patch, MagicMock +from mock import MagicMock, patch from readthedocs.builds.constants import LATEST -from readthedocs.projects.exceptions import RepositoryError from readthedocs.builds.models import Build -from readthedocs.projects.models import Project +from readthedocs.doc_builder.exceptions import VersionLockedError from readthedocs.projects import tasks - -from readthedocs.rtd_tests.utils import ( - create_git_branch, create_git_tag, delete_git_branch) -from readthedocs.rtd_tests.utils import make_test_git +from readthedocs.projects.exceptions import RepositoryError +from readthedocs.projects.models import Project from readthedocs.rtd_tests.base import RTDTestCase from readthedocs.rtd_tests.mocks.mock_api import mock_api -from readthedocs.doc_builder.exceptions import VersionLockedError +from readthedocs.rtd_tests.utils import ( + create_git_branch, + create_git_tag, + delete_git_branch, + make_test_git, +) class TestCeleryBuilding(RTDTestCase): diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 9938a2678..84c7fde76 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -19,6 +19,7 @@ from readthedocs.projects import tasks from readthedocs.projects.models import Feature, Project from readthedocs.rtd_tests.utils import create_git_submodule, make_git_repo + def create_load(config=None): """ Mock out the function of the build load function. diff --git a/readthedocs/rtd_tests/tests/test_core_tags.py b/readthedocs/rtd_tests/tests/test_core_tags.py index 20dd966ad..43a6b4f38 100644 --- a/readthedocs/rtd_tests/tests/test_core_tags.py +++ b/readthedocs/rtd_tests/tests/test_core_tags.py @@ -9,6 +9,7 @@ from readthedocs.builds.constants import LATEST from readthedocs.core.templatetags import core_tags from readthedocs.projects.models import Project + @override_settings(USE_SUBDOMAIN=False, PRODUCTION_DOMAIN='readthedocs.org') class CoreTagsTests(TestCase): fixtures = ["eric", "test_data"] diff --git a/readthedocs/rtd_tests/tests/test_core_utils.py b/readthedocs/rtd_tests/tests/test_core_utils.py index e480f6398..b44de84ce 100644 --- a/readthedocs/rtd_tests/tests/test_core_utils.py +++ b/readthedocs/rtd_tests/tests/test_core_utils.py @@ -9,6 +9,7 @@ from readthedocs.builds.models import Version from readthedocs.core.utils import slugify, trigger_build from readthedocs.projects.models import Project + class CoreUtilTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index 555cbec90..ff740302b 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -20,6 +20,7 @@ from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Feature, Project + class SphinxBuilderTest(TestCase): fixtures = ['test_data'] diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 5daa03df1..91e27907a 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -35,6 +35,7 @@ from readthedocs.rtd_tests.mocks.environment import EnvironmentMockGroup from readthedocs.rtd_tests.mocks.paths import fake_paths_lookup from readthedocs.rtd_tests.tests.test_config_integration import create_load + DUMMY_BUILD_ID = 123 SAMPLE_UNICODE = 'HérÉ îß sömê ünïçó∂é' SAMPLE_UTF8_BYTES = SAMPLE_UNICODE.encode('utf-8') diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index 56798ad84..2f9bf98ed 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -1,23 +1,20 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals, division, print_function -import mock -from mock import patch, mock_open import django_dynamic_fixture as fixture +import mock import pytest -import six - +from django.conf import settings from django.contrib.auth.models import User +from django.http import Http404 from django.test import TestCase from django.test.utils import override_settings -from django.http import Http404 -from django.conf import settings from django.urls import reverse +from mock import mock_open, patch -from readthedocs.rtd_tests.base import RequestFactoryTestMixin +from readthedocs.core.views.serve import _serve_symlink_docs from readthedocs.projects import constants from readthedocs.projects.models import Project -from readthedocs.core.views.serve import _serve_symlink_docs +from readthedocs.rtd_tests.base import RequestFactoryTestMixin @override_settings( diff --git a/readthedocs/rtd_tests/tests/test_domains.py b/readthedocs/rtd_tests/tests/test_domains.py index 963b07a91..377f0c8f6 100644 --- a/readthedocs/rtd_tests/tests/test_domains.py +++ b/readthedocs/rtd_tests/tests/test_domains.py @@ -12,6 +12,7 @@ from readthedocs.core.middleware import SubdomainMiddleware from readthedocs.projects.forms import DomainForm from readthedocs.projects.models import Domain, Project + class MiddlewareTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py index 21017e308..e4bb82a98 100644 --- a/readthedocs/rtd_tests/tests/test_extend.py +++ b/readthedocs/rtd_tests/tests/test_extend.py @@ -5,6 +5,7 @@ from readthedocs.core.utils.extend import ( get_override_class, ) + # Top level to ensure module name is correct class FooBase: def bar(self): diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index 692e85cd1..60f820a75 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -13,6 +13,7 @@ from readthedocs.restapi.views.footer_views import ( ) from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex + class Testmaker(APITestCase): fixtures = ['test_data'] url = '/api/v2/footer_html/?project=pip&version=latest&page=index&docroot=/' diff --git a/readthedocs/rtd_tests/tests/test_gold.py b/readthedocs/rtd_tests/tests/test_gold.py index 9475f4fd1..db7c9f927 100644 --- a/readthedocs/rtd_tests/tests/test_gold.py +++ b/readthedocs/rtd_tests/tests/test_gold.py @@ -6,6 +6,7 @@ from readthedocs.gold.models import LEVEL_CHOICES, GoldUser from readthedocs.projects.models import Project from readthedocs.rtd_tests.utils import create_user + class GoldViewTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_imported_file.py b/readthedocs/rtd_tests/tests/test_imported_file.py index d005fc9a1..fc1df6793 100644 --- a/readthedocs/rtd_tests/tests/test_imported_file.py +++ b/readthedocs/rtd_tests/tests/test_imported_file.py @@ -5,6 +5,7 @@ from django.test import TestCase from readthedocs.projects.models import ImportedFile, Project from readthedocs.projects.tasks import _manage_imported_files + base_dir = os.path.dirname(os.path.dirname(__file__)) diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py index 360fdb0f1..c35fffc78 100644 --- a/readthedocs/rtd_tests/tests/test_integrations.py +++ b/readthedocs/rtd_tests/tests/test_integrations.py @@ -11,6 +11,7 @@ from readthedocs.integrations.models import ( ) from readthedocs.projects.models import Project + class HttpExchangeTests(TestCase): """Test HttpExchange model by using existing views diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py index f7e5d6076..093957f71 100644 --- a/readthedocs/rtd_tests/tests/test_middleware.py +++ b/readthedocs/rtd_tests/tests/test_middleware.py @@ -15,6 +15,7 @@ from readthedocs.core.middleware import SubdomainMiddleware from readthedocs.projects.models import Domain, Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user + @override_settings(USE_SUBDOMAIN=True) class MiddlewareTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 4230f8f3e..0758e4d47 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -1,26 +1,30 @@ # -*- coding: utf-8 -*- """Notification tests""" -from __future__ import absolute_import from datetime import timedelta -import mock + import django_dynamic_fixture as fixture +import mock +from django.contrib.auth.models import AnonymousUser, User from django.http import HttpRequest from django.test import TestCase from django.test.utils import override_settings -from django.contrib.auth.models import User, AnonymousUser from django.utils import timezone from messages_extends.models import Message as PersistentMessage +from readthedocs.builds.models import Build from readthedocs.notifications import Notification, SiteNotification from readthedocs.notifications.backends import EmailBackend, SiteBackend -from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT +from readthedocs.notifications.constants import ( + ERROR, + INFO_NON_PERSISTENT, + WARNING_NON_PERSISTENT, +) from readthedocs.projects.models import Project from readthedocs.projects.notifications import ( - DeprecatedGitHubWebhookNotification, DeprecatedBuildWebhookNotification, + DeprecatedGitHubWebhookNotification, ) -from readthedocs.builds.models import Build @override_settings( diff --git a/readthedocs/rtd_tests/tests/test_oauth.py b/readthedocs/rtd_tests/tests/test_oauth.py index 45c2983bb..87d9439a1 100644 --- a/readthedocs/rtd_tests/tests/test_oauth.py +++ b/readthedocs/rtd_tests/tests/test_oauth.py @@ -14,6 +14,7 @@ from readthedocs.oauth.services import ( from readthedocs.projects import constants from readthedocs.projects.models import Project + class GitHubOAuthTests(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index f9b310a62..1db3332cb 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -1,14 +1,15 @@ import json import logging +from urllib.parse import urlencode, urlparse import mock from django.test import TestCase from django_dynamic_fixture import get -from urllib.parse import urlparse, urlencode from readthedocs.builds.models import Version from readthedocs.projects.models import Feature, Project + log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/tests/test_privacy.py b/readthedocs/rtd_tests/tests/test_privacy.py index 7844ece2e..a5977ad86 100644 --- a/readthedocs/rtd_tests/tests/test_privacy.py +++ b/readthedocs/rtd_tests/tests/test_privacy.py @@ -12,6 +12,7 @@ from readthedocs.projects import tasks from readthedocs.projects.forms import UpdateProjectForm from readthedocs.projects.models import Project + log = logging.getLogger(__name__) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index 52ec89d74..ed5fff8c2 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -1,21 +1,18 @@ -from __future__ import absolute_import -from __future__ import print_function import re +import mock from allauth.socialaccount.models import SocialAccount -from builtins import object from django.contrib.admindocs.views import extract_views_from_urlpatterns from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import get -import mock from taggit.models import Tag from readthedocs.builds.models import Build, BuildCommandResult from readthedocs.core.utils.tasks import TaskNoPermission from readthedocs.integrations.models import HttpExchange, Integration -from readthedocs.projects.models import Project, Domain, EnvironmentVariable -from readthedocs.oauth.models import RemoteRepository, RemoteOrganization +from readthedocs.oauth.models import RemoteOrganization, RemoteRepository +from readthedocs.projects.models import Domain, EnvironmentVariable, Project from readthedocs.rtd_tests.utils import create_user diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py index 239f093be..69dde65c8 100644 --- a/readthedocs/rtd_tests/tests/test_profile_views.py +++ b/readthedocs/rtd_tests/tests/test_profile_views.py @@ -3,6 +3,7 @@ from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import get + class ProfileViewsTest(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index b6bee71d0..5562d3e80 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -22,6 +22,7 @@ from readthedocs.projects.models import Project from readthedocs.projects.tasks import finish_inactive_builds from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex + class ProjectMixin: fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index eccfe9f33..166426263 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -25,7 +25,8 @@ from readthedocs.projects.forms import ( TranslationForm, UpdateProjectForm, ) -from readthedocs.projects.models import Project, EnvironmentVariable +from readthedocs.projects.models import EnvironmentVariable, Project + class TestProjectForms(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_project_querysets.py b/readthedocs/rtd_tests/tests/test_project_querysets.py index b8d5d9e1f..316525c91 100644 --- a/readthedocs/rtd_tests/tests/test_project_querysets.py +++ b/readthedocs/rtd_tests/tests/test_project_querysets.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- -from django.contrib.auth.models import User from datetime import timedelta import django_dynamic_fixture as fixture +from django.contrib.auth.models import User from django.test import TestCase -from readthedocs.projects.models import Project, Feature -from readthedocs.projects.querysets import (ParentRelatedProjectQuerySet, - ChildRelatedProjectQuerySet) +from readthedocs.projects.models import Feature, Project +from readthedocs.projects.querysets import ( + ChildRelatedProjectQuerySet, + ParentRelatedProjectQuerySet, +) class ProjectQuerySetTests(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index bd9cb76ef..473963b44 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -19,6 +19,7 @@ from readthedocs.projects.tasks import ( symlink_project, ) + def get_filesystem(path, top_level_path=None): """Recurse into path, return dictionary mapping of path and files diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py index 74aeed201..20dd5f5ec 100644 --- a/readthedocs/rtd_tests/tests/test_project_views.py +++ b/readthedocs/rtd_tests/tests/test_project_views.py @@ -23,6 +23,7 @@ from readthedocs.rtd_tests.base import ( WizardTestCase, ) + @patch('readthedocs.projects.views.private.trigger_build', lambda x: None) class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): diff --git a/readthedocs/rtd_tests/tests/test_redirects.py b/readthedocs/rtd_tests/tests/test_redirects.py index d53d55125..fad6cd2f4 100644 --- a/readthedocs/rtd_tests/tests/test_redirects.py +++ b/readthedocs/rtd_tests/tests/test_redirects.py @@ -11,6 +11,7 @@ from readthedocs.builds.models import Version from readthedocs.projects.models import Project from readthedocs.redirects.models import Redirect + @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False, APPEND_SLASH=False) class RedirectTests(TestCase): fixtures = ["eric", "test_data"] diff --git a/readthedocs/rtd_tests/tests/test_repo_parsing.py b/readthedocs/rtd_tests/tests/test_repo_parsing.py index d77c51ecb..85ffbbf99 100644 --- a/readthedocs/rtd_tests/tests/test_repo_parsing.py +++ b/readthedocs/rtd_tests/tests/test_repo_parsing.py @@ -3,6 +3,7 @@ from django.test import TestCase from readthedocs.projects.models import Project + class TestRepoParsing(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py index 929ccc8db..2918f1ce0 100644 --- a/readthedocs/rtd_tests/tests/test_resolver.py +++ b/readthedocs/rtd_tests/tests/test_resolver.py @@ -13,6 +13,7 @@ from readthedocs.projects.constants import PRIVATE from readthedocs.projects.models import Domain, Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user + @override_settings(PUBLIC_DOMAIN='readthedocs.org') class ResolverBase(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_restapi_client.py b/readthedocs/rtd_tests/tests/test_restapi_client.py index 1aeca51ee..7f75d0f5e 100644 --- a/readthedocs/rtd_tests/tests/test_restapi_client.py +++ b/readthedocs/rtd_tests/tests/test_restapi_client.py @@ -3,6 +3,7 @@ from django.test import TestCase from readthedocs.restapi.client import DrfJsonSerializer + class TestDrfJsonSerializer(TestCase): data = { 'proper': 'json' diff --git a/readthedocs/rtd_tests/tests/test_search_json_parsing.py b/readthedocs/rtd_tests/tests/test_search_json_parsing.py index b51ea834f..3836f72d1 100644 --- a/readthedocs/rtd_tests/tests/test_search_json_parsing.py +++ b/readthedocs/rtd_tests/tests/test_search_json_parsing.py @@ -4,6 +4,7 @@ from django.test import TestCase from readthedocs.search.parse_json import process_file + base_dir = os.path.dirname(os.path.dirname(__file__)) class TestHacks(TestCase): diff --git a/readthedocs/rtd_tests/tests/test_single_version.py b/readthedocs/rtd_tests/tests/test_single_version.py index de1bc9300..bb850d932 100644 --- a/readthedocs/rtd_tests/tests/test_single_version.py +++ b/readthedocs/rtd_tests/tests/test_single_version.py @@ -4,6 +4,7 @@ from django.test.utils import override_settings from readthedocs.projects.models import Project + @override_settings( USE_SUBDOMAIN=True, PUBLIC_DOMAIN='public.readthedocs.org', SERVE_PUBLIC_DOCS=True ) diff --git a/readthedocs/rtd_tests/tests/test_subprojects.py b/readthedocs/rtd_tests/tests/test_subprojects.py index 542b2d8b0..030fb1ecc 100644 --- a/readthedocs/rtd_tests/tests/test_subprojects.py +++ b/readthedocs/rtd_tests/tests/test_subprojects.py @@ -8,6 +8,7 @@ from readthedocs.projects.forms import ProjectRelationshipForm from readthedocs.projects.models import Project, ProjectRelationship from readthedocs.rtd_tests.utils import create_user + class SubprojectFormTests(TestCase): def test_empty_child(self): diff --git a/readthedocs/rtd_tests/tests/test_sync_versions.py b/readthedocs/rtd_tests/tests/test_sync_versions.py index 731cc0675..6c41de5b8 100644 --- a/readthedocs/rtd_tests/tests/test_sync_versions.py +++ b/readthedocs/rtd_tests/tests/test_sync_versions.py @@ -10,6 +10,7 @@ from readthedocs.builds.constants import BRANCH, STABLE, TAG from readthedocs.builds.models import Version from readthedocs.projects.models import Project + class TestSyncVersions(TestCase): fixtures = ['eric', 'test_data'] diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py index 55a4a8400..4a10e1495 100644 --- a/readthedocs/rtd_tests/tests/test_urls.py +++ b/readthedocs/rtd_tests/tests/test_urls.py @@ -1,6 +1,7 @@ from django.test import TestCase from django.urls import NoReverseMatch, reverse + class WipeUrlTests(TestCase): def test_wipe_no_params(self): diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py index 23875955f..b690ef3db 100644 --- a/readthedocs/rtd_tests/tests/test_version_commit_name.py +++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py @@ -6,6 +6,7 @@ from readthedocs.builds.models import Version from readthedocs.projects.constants import REPO_TYPE_GIT, REPO_TYPE_HG from readthedocs.projects.models import Project + class VersionCommitNameTests(TestCase): def test_branch_name_unicode_non_ascii(self): unicode_name = b'abc_\xd1\x84_\xe2\x99\x98'.decode('utf-8') diff --git a/readthedocs/rtd_tests/tests/test_version_config.py b/readthedocs/rtd_tests/tests/test_version_config.py index 4fd8d0854..2c79686aa 100644 --- a/readthedocs/rtd_tests/tests/test_version_config.py +++ b/readthedocs/rtd_tests/tests/test_version_config.py @@ -4,6 +4,7 @@ from django_dynamic_fixture import get from readthedocs.builds.models import Build, Version from readthedocs.projects.models import Project + class VersionConfigTests(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py index 44d8500f2..a193156e1 100644 --- a/readthedocs/rtd_tests/tests/test_version_slug.py +++ b/readthedocs/rtd_tests/tests/test_version_slug.py @@ -6,6 +6,7 @@ from readthedocs.builds.models import Version from readthedocs.builds.version_slug import VERSION_SLUG_REGEX, VersionSlugField from readthedocs.projects.models import Project + class VersionSlugPatternTests(TestCase): pattern = re.compile('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX)) diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py index 56b669979..ca9cc42ed 100644 --- a/readthedocs/rtd_tests/tests/test_views.py +++ b/readthedocs/rtd_tests/tests/test_views.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +from urllib.parse import urlsplit + import mock from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import get, new -from urllib.parse import urlsplit from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Build @@ -12,6 +13,7 @@ from readthedocs.core.permissions import AdminPermission from readthedocs.projects.forms import UpdateProjectForm from readthedocs.projects.models import ImportedFile, Project + class Testmaker(TestCase): def setUp(self): diff --git a/readthedocs/rtd_tests/utils.py b/readthedocs/rtd_tests/utils.py index a5768a19d..323dc07ad 100644 --- a/readthedocs/rtd_tests/utils.py +++ b/readthedocs/rtd_tests/utils.py @@ -15,6 +15,7 @@ from django_dynamic_fixture import new from readthedocs.doc_builder.base import restoring_chdir + log = logging.getLogger(__name__) diff --git a/readthedocs/search/tests/conftest.py b/readthedocs/search/tests/conftest.py index ab46332f7..1577b6b04 100644 --- a/readthedocs/search/tests/conftest.py +++ b/readthedocs/search/tests/conftest.py @@ -15,6 +15,7 @@ from readthedocs.search.indexes import ( from .dummy_data import ALL_PROJECTS, DUMMY_PAGE_JSON + @pytest.fixture(autouse=True) def mock_elastic_index(mocker): index_name = ''.join([random.choice(string.ascii_letters) for _ in range(5)]) diff --git a/readthedocs/search/tests/dummy_data.py b/readthedocs/search/tests/dummy_data.py index fbd4eed1f..f46449374 100644 --- a/readthedocs/search/tests/dummy_data.py +++ b/readthedocs/search/tests/dummy_data.py @@ -1,6 +1,7 @@ import json import os + _DATA_FILES = { 'pipeline': ['installation.json', 'signals.json'], 'kuma': ['documentation.json', 'docker.json'], diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py index 37072e949..d5ac18baf 100644 --- a/readthedocs/search/tests/test_views.py +++ b/readthedocs/search/tests/test_views.py @@ -11,6 +11,7 @@ from readthedocs.builds.models import Version from readthedocs.projects.models import Project from readthedocs.search.tests.utils import get_search_query_from_project_file + @pytest.mark.django_db @pytest.mark.search class TestElasticSearch: diff --git a/readthedocs/search/tests/utils.py b/readthedocs/search/tests/utils.py index 90ce5f705..a48ea83dd 100644 --- a/readthedocs/search/tests/utils.py +++ b/readthedocs/search/tests/utils.py @@ -1,5 +1,6 @@ from readthedocs.search.tests.dummy_data import DUMMY_PAGE_JSON + def get_search_query_from_project_file(project_slug, page_num=0, data_type='title'): """Return search query from the project's page file. Query is generated from the value of `data_type` diff --git a/readthedocs/vcs_support/tests.py b/readthedocs/vcs_support/tests.py index df9702ce7..2b16090ba 100644 --- a/readthedocs/vcs_support/tests.py +++ b/readthedocs/vcs_support/tests.py @@ -7,6 +7,7 @@ from django.conf import settings from readthedocs.vcs_support import utils + TEST_STATICS = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_statics') From 4aded8b1db59f6fc09bd54e58e23e6b6952d9d1b Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:20:09 +0100 Subject: [PATCH 55/65] Remove skipif for six.PY2 --- readthedocs/rtd_tests/tests/test_doc_serving.py | 1 - 1 file changed, 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index 2f9bf98ed..7e06a2a3b 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -161,7 +161,6 @@ class TestPublicDocs(BaseDocServing): read_data='My own robots.txt', ) @patch('readthedocs.core.views.serve.os') - @pytest.mark.skipif(six.PY2, reason='In Python2 the mock is __builtins__.open') def test_custom_robots_txt(self, os_mock, open_mock): os_mock.path.exists.return_value = True self.public.versions.update(active=True, built=True) From 447e3b722ef5663effb6c3c8d1935483f1f049dd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:40:49 +0100 Subject: [PATCH 56/65] Update common submodule with the proper PR config --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 46aad68c9..2c4286032 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 46aad68c905ff843559b39cb52b5d54e586115c4 +Subproject commit 2c42860327916ec66f3aed7cf3d7bab809438ab4 From b5bc9c7ab2fd32d1d8d79fc0b43255468254d11a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:41:15 +0100 Subject: [PATCH 57/65] pre-commit all the files --- readthedocs/analytics/tests.py | 1 + readthedocs/builds/models.py | 98 +++-- readthedocs/config/config.py | 56 +-- readthedocs/config/tests/test_config.py | 6 +- readthedocs/config/tests/test_find.py | 1 + readthedocs/config/tests/test_parser.py | 1 + readthedocs/config/tests/test_utils.py | 1 + readthedocs/config/tests/test_validation.py | 2 +- readthedocs/config/tests/utils.py | 8 +- readthedocs/core/tests/test_signals.py | 15 +- readthedocs/core/urls/subdomain.py | 32 +- readthedocs/core/views/hooks.py | 62 +-- readthedocs/core/views/serve.py | 25 +- .../migrations/0002_rename_last_4_digits.py | 2 +- readthedocs/gold/tests/test_forms.py | 7 +- readthedocs/gold/tests/test_signals.py | 5 +- .../oauth/migrations/0003_move_github.py | 10 +- .../0004_drop_github_and_bitbucket_models.py | 2 +- .../migrations/0006_move_oauth_source.py | 12 +- readthedocs/projects/admin.py | 41 +- readthedocs/projects/exceptions.py | 4 +- readthedocs/projects/forms.py | 62 +-- .../migrations/0007_migrate_canonical_data.py | 6 +- .../migrations/0009_add_domain_field.py | 1 - .../migrations/0010_migrate_domain_data.py | 8 +- .../projects/migrations/0011_delete-url.py | 2 +- .../migrations/0016_build-queue-name.py | 7 +- .../migrations/0020_add-api-project-proxy.py | 2 +- .../0021_add-webhook-deprecation-feature.py | 2 +- .../migrations/0023_migrate-alias-slug.py | 2 +- readthedocs/projects/models.py | 367 +++++++++++------- readthedocs/projects/notifications.py | 5 +- readthedocs/projects/signals.py | 17 +- readthedocs/projects/tasks.py | 2 +- readthedocs/projects/urls/private.py | 315 ++++++++------- readthedocs/projects/views/private.py | 5 +- readthedocs/rtd_tests/base.py | 3 +- readthedocs/rtd_tests/files/api.fjson | 2 +- .../fixtures/sample_repo/source/index.rst | 1 - .../rtd_tests/fixtures/spec/v2/schema.yml | 2 +- readthedocs/rtd_tests/mocks/environment.py | 7 +- readthedocs/rtd_tests/mocks/mock_api.py | 1 + readthedocs/rtd_tests/mocks/paths.py | 1 + .../tests/projects/test_admin_actions.py | 3 +- readthedocs/rtd_tests/tests/test_api.py | 20 +- .../rtd_tests/tests/test_api_permissions.py | 1 + .../tests/test_api_version_compare.py | 1 + readthedocs/rtd_tests/tests/test_backend.py | 6 +- .../rtd_tests/tests/test_backend_svn.py | 3 +- .../rtd_tests/tests/test_build_config.py | 6 +- readthedocs/rtd_tests/tests/test_builds.py | 15 +- readthedocs/rtd_tests/tests/test_celery.py | 10 +- .../tests/test_config_integration.py | 4 +- readthedocs/rtd_tests/tests/test_core_tags.py | 4 +- .../rtd_tests/tests/test_core_utils.py | 12 +- .../rtd_tests/tests/test_doc_serving.py | 1 - readthedocs/rtd_tests/tests/test_domains.py | 4 +- readthedocs/rtd_tests/tests/test_extend.py | 5 +- readthedocs/rtd_tests/tests/test_gold.py | 1 + .../rtd_tests/tests/test_imported_file.py | 3 +- .../rtd_tests/tests/test_integrations.py | 10 +- .../rtd_tests/tests/test_notifications.py | 6 +- .../rtd_tests/tests/test_post_commit_hooks.py | 339 ++++++++-------- readthedocs/rtd_tests/tests/test_privacy.py | 39 +- .../rtd_tests/tests/test_privacy_urls.py | 5 +- .../rtd_tests/tests/test_profile_views.py | 1 + .../rtd_tests/tests/test_project_forms.py | 2 +- .../rtd_tests/tests/test_project_symlinks.py | 24 +- .../rtd_tests/tests/test_project_views.py | 32 +- readthedocs/rtd_tests/tests/test_redirects.py | 11 +- .../tests/test_search_json_parsing.py | 1 + .../rtd_tests/tests/test_single_version.py | 1 + .../rtd_tests/tests/test_subprojects.py | 1 + .../rtd_tests/tests/test_sync_versions.py | 37 +- readthedocs/rtd_tests/tests/test_urls.py | 1 + .../tests/test_version_commit_name.py | 1 + .../rtd_tests/tests/test_version_config.py | 1 + .../rtd_tests/tests/test_version_slug.py | 3 +- readthedocs/rtd_tests/utils.py | 4 +- readthedocs/search/tests/conftest.py | 1 + readthedocs/search/tests/data/docs/story.json | 2 +- .../search/tests/data/docs/wiping.json | 2 +- .../search/tests/data/kuma/docker.json | 2 +- .../search/tests/data/kuma/documentation.json | 2 +- .../tests/data/pipeline/installation.json | 2 +- .../search/tests/data/pipeline/signals.json | 2 +- readthedocs/search/tests/dummy_data.py | 3 +- readthedocs/search/tests/test_views.py | 15 +- readthedocs/search/tests/utils.py | 7 +- readthedocs/vcs_support/tests.py | 2 +- 90 files changed, 1012 insertions(+), 857 deletions(-) diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 3a18165eb..d3507d864 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from .utils import anonymize_ip_address, anonymize_user_agent diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index eaf4614a1..e4fcad037 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Models for the builds app.""" import logging @@ -49,7 +50,8 @@ from .version_slug import VersionSlugField DEFAULT_VERSION_PRIVACY_LEVEL = getattr( - settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public') + settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public' +) log = logging.getLogger(__name__) @@ -89,7 +91,8 @@ class Version(models.Model): #: filesystem to determine how the paths for this version are called. It #: must not be used for any other identifying purposes. slug = VersionSlugField( - _('Slug'), max_length=255, populate_from='verbose_name') + _('Slug'), max_length=255, populate_from='verbose_name' + ) supported = models.BooleanField(_('Supported'), default=True) active = models.BooleanField(_('Active'), default=False) @@ -113,7 +116,8 @@ class Version(models.Model): permissions = ( # Translators: Permission around whether a user can view the # version - ('view_version', _('View Version')),) + ('view_version', _('View Version')), + ) def __str__(self): return ugettext( @@ -121,7 +125,8 @@ class Version(models.Model): version=self.verbose_name, project=self.project, pk=self.pk, - )) + ) + ) @property def config(self): @@ -132,9 +137,8 @@ class Version(models.Model): :rtype: dict """ last_build = ( - self.builds.filter(state='finished', success=True) - .order_by('-date') - .first() + self.builds.filter(state='finished', + success=True).order_by('-date').first() ) return last_build.config @@ -177,7 +181,9 @@ class Version(models.Model): # If we came that far it's not a special version nor a branch or tag. # Therefore just return the identifier to make a safe guess. - log.debug('TODO: Raise an exception here. Testing what cases it happens') + log.debug( + 'TODO: Raise an exception here. Testing what cases it happens' + ) return self.identifier def get_absolute_url(self): @@ -191,7 +197,8 @@ class Version(models.Model): ) private = self.privacy_level == PRIVATE return self.project.get_docs_url( - version_slug=self.slug, private=private) + version_slug=self.slug, private=private + ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ """Add permissions to the Version for all owners on save.""" @@ -200,7 +207,8 @@ class Version(models.Model): for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( - type='app', task=tasks.symlink_project, args=[self.project.pk]) + type='app', task=tasks.symlink_project, args=[self.project.pk] + ) return obj def delete(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -246,19 +254,23 @@ class Version(models.Model): data['PDF'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['HTML'] = project.get_production_media_url( - 'htmlzip', self.slug) + 'htmlzip', self.slug + ) if project.has_epub(self.slug): data['Epub'] = project.get_production_media_url( - 'epub', self.slug) + 'epub', self.slug + ) else: if project.has_pdf(self.slug): data['pdf'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['htmlzip'] = project.get_production_media_url( - 'htmlzip', self.slug) + 'htmlzip', self.slug + ) if project.has_epub(self.slug): data['epub'] = project.get_production_media_url( - 'epub', self.slug) + 'epub', self.slug + ) return data def get_conf_py_path(self): @@ -284,9 +296,8 @@ class Version(models.Model): for type_ in ('pdf', 'epub', 'htmlzip'): paths.append( - self.project.get_production_media_path( - type_=type_, - version_slug=self.slug), + self.project + .get_production_media_path(type_=type_, version_slug=self.slug), ) paths.append(self.project.rtd_build_path(version=self.slug)) @@ -308,7 +319,8 @@ class Version(models.Model): log.exception('Build path cleanup failed') def get_github_url( - self, docroot, filename, source_suffix='.rst', action='view'): + self, docroot, filename, source_suffix='.rst', action='view' + ): """ Return a GitHub URL for a given filename. @@ -350,7 +362,8 @@ class Version(models.Model): ) def get_gitlab_url( - self, docroot, filename, source_suffix='.rst', action='view'): + self, docroot, filename, source_suffix='.rst', action='view' + ): repo_url = self.project.repo if 'gitlab' not in repo_url: return '' @@ -447,13 +460,17 @@ class Build(models.Model): """Build data.""" project = models.ForeignKey( - Project, verbose_name=_('Project'), related_name='builds') + Project, verbose_name=_('Project'), related_name='builds' + ) version = models.ForeignKey( - Version, verbose_name=_('Version'), null=True, related_name='builds') + Version, verbose_name=_('Version'), null=True, related_name='builds' + ) type = models.CharField( - _('Type'), max_length=55, choices=BUILD_TYPES, default='html') + _('Type'), max_length=55, choices=BUILD_TYPES, default='html' + ) state = models.CharField( - _('State'), max_length=55, choices=BUILD_STATE, default='finished') + _('State'), max_length=55, choices=BUILD_STATE, default='finished' + ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) @@ -463,16 +480,19 @@ class Build(models.Model): error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( - _('Commit'), max_length=255, null=True, blank=True) + _('Commit'), max_length=255, null=True, blank=True + ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( - _('Builder'), max_length=255, null=True, blank=True) + _('Builder'), max_length=255, null=True, blank=True + ) cold_storage = models.NullBooleanField( - _('Cold Storage'), help_text='Build steps stored outside the database.') + _('Cold Storage'), help_text='Build steps stored outside the database.' + ) # Manager @@ -499,14 +519,11 @@ class Build(models.Model): date = self.date or timezone.now() if self.project is not None and self.version is not None: return ( - Build.objects - .filter( + Build.objects.filter( project=self.project, version=self.version, date__lt=date, - ) - .order_by('-date') - .first() + ).order_by('-date').first() ) return None @@ -516,9 +533,9 @@ class Build(models.Model): Get the config used for this build. Since we are saving the config into the JSON field only when it differs - from the previous one, this helper returns the correct JSON used in - this Build object (it could be stored in this object or one of the - previous ones). + from the previous one, this helper returns the correct JSON used in this + Build object (it could be stored in this object or one of the previous + ones). """ if self.CONFIG_KEY in self._config: return Build.objects.get(pk=self._config[self.CONFIG_KEY])._config @@ -546,8 +563,8 @@ class Build(models.Model): """ if self.pk is None or self._config_changed: previous = self.previous - if (previous is not None and - self._config and self._config == previous.config): + if (previous is not None and self._config and + self._config == previous.config): previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} super().save(*args, **kwargs) @@ -561,7 +578,8 @@ class Build(models.Model): self.project.users.all().values_list('username', flat=True), ), pk=self.pk, - )) + ) + ) def get_absolute_url(self): return reverse('builds_detail', args=[self.project.slug, self.pk]) @@ -602,7 +620,8 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): """Build command for a ``Build``.""" build = models.ForeignKey( - Build, verbose_name=_('Build'), related_name='commands') + Build, verbose_name=_('Build'), related_name='commands' + ) command = models.TextField(_('Command')) description = models.TextField(_('Description'), blank=True) @@ -621,7 +640,8 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): def __str__(self): return ( ugettext('Build command {pk} for build {build}') - .format(pk=self.pk, build=self.build)) + .format(pk=self.pk, build=self.build) + ) @property def run_time(self): diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index c860b018c..a3a6e7877 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -131,9 +131,15 @@ class BuildConfigBase: """ PUBLIC_ATTRIBUTES = [ - 'version', 'formats', 'python', - 'conda', 'build', 'doctype', - 'sphinx', 'mkdocs', 'submodules', + 'version', + 'formats', + 'python', + 'conda', + 'build', + 'doctype', + 'sphinx', + 'mkdocs', + 'submodules', ] version = None @@ -336,14 +342,11 @@ class BuildConfigV1(BuildConfigBase): if ':' not in build['image']: # Prepend proper image name to user's image name build['image'] = '{}:{}'.format( - DOCKER_DEFAULT_IMAGE, - build['image'] + DOCKER_DEFAULT_IMAGE, build['image'] ) # Update docker default settings from image name if build['image'] in DOCKER_IMAGE_SETTINGS: - self.env_config.update( - DOCKER_IMAGE_SETTINGS[build['image']] - ) + self.env_config.update(DOCKER_IMAGE_SETTINGS[build['image']]) # Allow to override specific project config_image = self.defaults.get('build_image') @@ -368,22 +371,22 @@ class BuildConfigV1(BuildConfigBase): raw_python = self.raw_config['python'] if not isinstance(raw_python, dict): self.error( - 'python', - self.PYTHON_INVALID_MESSAGE, - code=PYTHON_INVALID) + 'python', self.PYTHON_INVALID_MESSAGE, code=PYTHON_INVALID, + ) # Validate use_system_site_packages. if 'use_system_site_packages' in raw_python: - with self.catch_validation_error( - 'python.use_system_site_packages'): + with self.catch_validation_error('python.use_system_site_packages'): python['use_system_site_packages'] = validate_bool( - raw_python['use_system_site_packages']) + raw_python['use_system_site_packages'] + ) # Validate pip_install. if 'pip_install' in raw_python: with self.catch_validation_error('python.pip_install'): python['install_with_pip'] = validate_bool( - raw_python['pip_install']) + raw_python['pip_install'] + ) # Validate extra_requirements. if 'extra_requirements' in raw_python: @@ -392,13 +395,13 @@ class BuildConfigV1(BuildConfigBase): self.error( 'python.extra_requirements', self.PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE, - code=PYTHON_INVALID) + code=PYTHON_INVALID + ) if not python['install_with_pip']: python['extra_requirements'] = [] else: for extra_name in raw_extra_requirements: - with self.catch_validation_error( - 'python.extra_requirements'): + with self.catch_validation_error('python.extra_requirements'): python['extra_requirements'].append( validate_string(extra_name) ) @@ -407,7 +410,8 @@ class BuildConfigV1(BuildConfigBase): if 'setup_py_install' in raw_python: with self.catch_validation_error('python.setup_py_install'): python['install_with_setup'] = validate_bool( - raw_python['setup_py_install']) + raw_python['setup_py_install'] + ) if 'version' in raw_python: with self.catch_validation_error('python.version'): @@ -813,9 +817,8 @@ class BuildConfigV2(BuildConfigBase): """ Validates that the doctype is the same as the admin panel. - This a temporal validation, as the configuration file - should support per version doctype, but we need to - adapt the rtd code for that. + This a temporal validation, as the configuration file should support per + version doctype, but we need to adapt the rtd code for that. """ dashboard_doctype = self.defaults.get('doctype', 'sphinx') if self.doctype != dashboard_doctype: @@ -887,8 +890,8 @@ class BuildConfigV2(BuildConfigBase): """ Checks that we don't have extra keys (invalid ones). - This should be called after all the validations are done - and all keys are popped from `self.raw_config`. + This should be called after all the validations are done and all keys + are popped from `self.raw_config`. """ msg = ( 'Invalid configuration option: {}. ' @@ -978,10 +981,7 @@ def load(path, env_config): filename = find_one(path, CONFIG_FILENAME_REGEX) if not filename: - raise ConfigError( - 'No configuration file found', - code=CONFIG_REQUIRED - ) + raise ConfigError('No configuration file found', code=CONFIG_REQUIRED) with open(filename, 'r') as configuration_file: try: config = parse(configuration_file.read()) diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 09b57adec..29fbac1be 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -121,7 +121,7 @@ def test_load_unknow_version(tmpdir): def test_yaml_extension(tmpdir): - """Make sure it's capable of loading the 'readthedocs' file with a 'yaml' extension.""" + """Make sure loading the 'readthedocs' file with a 'yaml' extension.""" apply_fs(tmpdir, { 'readthedocs.yaml': textwrap.dedent( ''' @@ -643,8 +643,8 @@ def test_raise_config_not_supported(): @pytest.mark.parametrize('correct_config_filename', - [prefix + 'readthedocs.' + extension for prefix in {"", "."} - for extension in {"yml", "yaml"}]) + [prefix + 'readthedocs.' + extension for prefix in {'', '.'} + for extension in {'yml', 'yaml'}]) def test_config_filenames_regex(correct_config_filename): assert re.match(CONFIG_FILENAME_REGEX, correct_config_filename) diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index b982245fb..ab813cdb6 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os from readthedocs.config.find import find_one diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py index d140c8c0c..9062f32ca 100644 --- a/readthedocs/config/tests/test_parser.py +++ b/readthedocs/config/tests/test_parser.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from io import StringIO from pytest import raises diff --git a/readthedocs/config/tests/test_utils.py b/readthedocs/config/tests/test_utils.py index 04f019276..3d4b57254 100644 --- a/readthedocs/config/tests/test_utils.py +++ b/readthedocs/config/tests/test_utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from .utils import apply_fs diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py index 780883e00..8a0e98b98 100644 --- a/readthedocs/config/tests/test_validation.py +++ b/readthedocs/config/tests/test_validation.py @@ -79,7 +79,7 @@ class TestValidateList: def test_it_rejects_string_types(self): with raises(ValidationError) as excinfo: - result = validate_list('choice') + validate_list('choice') assert excinfo.value.code == INVALID_LIST diff --git a/readthedocs/config/tests/utils.py b/readthedocs/config/tests/utils.py index 1e390fa8a..4dd6a5331 100644 --- a/readthedocs/config/tests/utils.py +++ b/readthedocs/config/tests/utils.py @@ -1,9 +1,11 @@ +# -*- coding: utf-8 -*- def apply_fs(tmpdir, contents): """ - Create the directory structure specified in ``contents``. It's a dict of - filenames as keys and the file contents as values. If the value is another - dict, it's a subdirectory. + Create the directory structure specified in ``contents``. + + It's a dict of filenames as keys and the file contents as values. If the + value is another dict, it's a subdirectory. """ for filename, content in contents.items(): if hasattr(content, 'items'): diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py index b6fa0d8ad..40dccdaa3 100644 --- a/readthedocs/core/tests/test_signals.py +++ b/readthedocs/core/tests/test_signals.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture import pytest from django.contrib.auth.models import User @@ -11,11 +12,9 @@ class TestProjectOrganizationSignal: @pytest.mark.parametrize('model_class', [Project, RemoteOrganization]) def test_project_organization_get_deleted_upon_user_delete(self, model_class): - """ - If the user has Project or RemoteOrganization where he is the only user, - upon deleting his account, the Project or RemoteOrganization should also get - deleted. - """ + """If the user has Project or RemoteOrganization where he is the only + user, upon deleting his account, the Project or RemoteOrganization + should also get deleted.""" obj = django_dynamic_fixture.get(model_class) user1 = django_dynamic_fixture.get(User) @@ -32,10 +31,8 @@ class TestProjectOrganizationSignal: @pytest.mark.parametrize('model_class', [Project, RemoteOrganization]) def test_multiple_users_project_organization_not_delete(self, model_class): - """ - Check Project or RemoteOrganization which have multiple users do not get deleted - when any of the user delete his account. - """ + """Check Project or RemoteOrganization which have multiple users do not + get deleted when any of the user delete his account.""" obj = django_dynamic_fixture.get(model_class) user1 = django_dynamic_fixture.get(User) diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 75df07eb3..506688b75 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -23,28 +23,28 @@ handler404 = server_error_404 subdomain_urls = [ url(r'robots.txt$', robots_txt, name='robots_txt'), - - url(r'^(?:|projects/(?P{project_slug})/)' + url( + r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), - redirect_page_with_filename, - name='docs_detail'), - - url((r'^(?:|projects/(?P{project_slug})/)$').format(**pattern_opts), - redirect_project_slug, - name='redirect_project_slug'), - - url((r'^(?:|projects/(?P{project_slug})/)' - r'(?P{lang_slug})/' - r'(?P{version_slug})/' - r'(?P{filename_slug})$'.format(**pattern_opts)), - serve_docs, - name='docs_detail'), + redirect_page_with_filename, name='docs_detail', + ), + url((r'^(?:|projects/(?P{project_slug})/)$').format( + **pattern_opts + ), redirect_project_slug, name='redirect_project_slug',), + url(( + r'^(?:|projects/(?P{project_slug})/)' + r'(?P{lang_slug})/' + r'(?P{version_slug})/' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve_docs, name='docs_detail',), ] groups = [subdomain_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): - groups.insert(0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)) + groups.insert( + 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT,), + ) urlpatterns = reduce(add, groups) diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 624f325d9..6302e9302 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Views pertaining to builds.""" import json @@ -41,13 +42,14 @@ def _build_version(project, slug, already_built=()): version = project.versions.filter(active=True, slug=slug).first() if version and slug not in already_built: log.info( - "(Version build) Building %s:%s", - project.slug, version.slug, + '(Version build) Building %s:%s', + project.slug, + version.slug, ) trigger_build(project=project, version=version, force=True) return slug - log.info("(Version build) Not Building %s", slug) + log.info('(Version build) Not Building %s', slug) return None @@ -64,8 +66,9 @@ def build_branches(project, branch_list): for branch in branch_list: versions = project.versions_from_branch_name(branch) for version in versions: - log.info("(Branch Build) Processing %s:%s", - project.slug, version.slug) + log.info( + '(Branch Build) Processing %s:%s', project.slug, version.slug + ) ret = _build_version(project, version.slug, already_built=to_build) if ret: to_build.add(ret) @@ -89,9 +92,7 @@ def sync_versions(project): try: version_identifier = project.get_default_branch() version = ( - project.versions - .filter(identifier=version_identifier) - .first() + project.versions.filter(identifier=version_identifier,).first() ) if not version: log.info('Unable to sync from %s version', version_identifier) @@ -114,10 +115,9 @@ def get_project_from_url(url): def log_info(project, msg): - log.info(constants.LOG_TEMPLATE - .format(project=project, - version='', - msg=msg)) + log.info( + constants.LOG_TEMPLATE.format(project=project, version='', msg=msg,), + ) def _build_url(url, projects, branches): @@ -127,7 +127,7 @@ def _build_url(url, projects, branches): Check each of the ``branches`` to see if they are active and should be built. """ - ret = "" + ret = '' all_built = {} all_not_building = {} @@ -151,14 +151,16 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: msg = '(URL Build) Build Started: {} [{}]'.format( - url, ' '.join(built)) + url, ' '.join(built) + ) log_info(project_slug, msg=msg) ret += msg for project_slug, not_building in list(all_not_building.items()): if not_building: msg = '(URL Build) Not Building: {} [{}]'.format( - url, ' '.join(not_building)) + url, ' '.join(not_building) + ) log_info(project_slug, msg=msg) ret += msg @@ -204,15 +206,13 @@ def github_build(request): # noqa: D205 if repo_projects: log.info( 'GitHub webhook search: url=%s branches=%s', - http_search_url, - branches + http_search_url, branches, ) ssh_projects = get_project_from_url(ssh_search_url) if ssh_projects: log.info( - 'GitHub webhook search: url=%s branches=%s', - ssh_search_url, - branches + 'GitHub webhook search: url=%s branches=%s', ssh_search_url, + branches, ) projects = repo_projects | ssh_projects return _build_url(http_search_url, projects, branches) @@ -287,10 +287,11 @@ def bitbucket_build(request): else: data = json.loads(request.body) - version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1 + version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1 # yapf: disabled # noqa if version == 1: - branches = [commit.get('branch', '') - for commit in data['commits']] + branches = [ + commit.get('branch', '') for commit in data['commits'] + ] repository = data['repository'] if not repository['absolute_url']: return HttpResponse('Invalid request', status=400) @@ -299,8 +300,7 @@ def bitbucket_build(request): ) elif version == 2: changes = data['push']['changes'] - branches = [change['new']['name'] - for change in changes] + branches = [change['new']['name'] for change in changes] if not data['repository']['full_name']: return HttpResponse('Invalid request', status=400) search_url = 'bitbucket.org/{}'.format( @@ -352,10 +352,12 @@ def generic_build(request, project_id_or_slug=None): project = Project.objects.get(slug=project_id_or_slug) except (Project.DoesNotExist, ValueError): log.exception( - "(Incoming Generic Build) Repo not found: %s", - project_id_or_slug) + '(Incoming Generic Build) Repo not found: %s', + project_id_or_slug + ) return HttpResponseNotFound( - 'Repo not found: %s' % project_id_or_slug) + 'Repo not found: %s' % project_id_or_slug + ) # This endpoint doesn't require authorization, we shouldn't allow builds to # be triggered from this any longer. Deprecation plan is to selectively # allow access to this endpoint for now. @@ -364,11 +366,11 @@ def generic_build(request, project_id_or_slug=None): if request.method == 'POST': slug = request.POST.get('version_slug', project.default_version) log.info( - "(Incoming Generic Build) %s [%s]", + '(Incoming Generic Build) %s [%s]', project.slug, slug, ) _build_version(project, slug) else: - return HttpResponse("You must POST to this resource.") + return HttpResponse('You must POST to this resource.') return redirect('builds_project_list', project.slug) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index fa5245cfe..388c8ce76 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """ Doc serving from Python. @@ -55,8 +56,11 @@ def map_subproject_slug(view_func): .. warning:: Does not take into account any kind of privacy settings. """ + @wraps(view_func) - def inner_view(request, subproject=None, subproject_slug=None, *args, **kwargs): # noqa + def inner_view( + request, subproject=None, subproject_slug=None, *args, **kwargs + ): # noqa if subproject is None and subproject_slug: # Try to fetch by subproject alias first, otherwise we might end up # redirected to an unrelated project. @@ -82,8 +86,11 @@ def map_project_slug(view_func): .. warning:: Does not take into account any kind of privacy settings. """ + @wraps(view_func) - def inner_view(request, project=None, project_slug=None, *args, **kwargs): # noqa + def inner_view( + request, project=None, project_slug=None, *args, **kwargs + ): # noqa if project is None: if not project_slug: project_slug = request.slug @@ -108,7 +115,8 @@ def redirect_project_slug(request, project, subproject): # pylint: disable=unus def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa """Redirect /page/file.html to /en/latest/file.html.""" return HttpResponseRedirect( - resolve(subproject or project, filename=filename)) + resolve(subproject or project, filename=filename) + ) def _serve_401(request, project): @@ -126,7 +134,8 @@ def _serve_file(request, filename, basepath): # Serve from Nginx content_type, encoding = mimetypes.guess_type( - os.path.join(basepath, filename)) + os.path.join(basepath, filename) + ) content_type = content_type or 'application/octet-stream' response = HttpResponse(content_type=content_type) if encoding: @@ -153,8 +162,9 @@ def _serve_file(request, filename, basepath): @map_subproject_slug def serve_docs( request, project, subproject, lang_slug=None, version_slug=None, - filename=''): - """Exists to map existing proj, lang, version, filename views to the file format.""" + filename='' +): + """Map existing proj, lang, version, filename views to the file format.""" if not version_slug: version_slug = project.get_default_version() try: @@ -219,7 +229,8 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''): files_tried.append(os.path.join(basepath, filename)) raise Http404( - 'File not found. Tried these files: %s' % ','.join(files_tried)) + 'File not found. Tried these files: %s' % ','.join(files_tried) + ) @map_project_slug diff --git a/readthedocs/gold/migrations/0002_rename_last_4_digits.py b/readthedocs/gold/migrations/0002_rename_last_4_digits.py index fc0aad453..478681e4a 100644 --- a/readthedocs/gold/migrations/0002_rename_last_4_digits.py +++ b/readthedocs/gold/migrations/0002_rename_last_4_digits.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-07-16 15:45 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/readthedocs/gold/tests/test_forms.py b/readthedocs/gold/tests/test_forms.py index 074102336..4231c713d 100644 --- a/readthedocs/gold/tests/test_forms.py +++ b/readthedocs/gold/tests/test_forms.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture import mock from django.contrib.auth.models import User @@ -31,7 +32,7 @@ class GoldSubscriptionFormTests(TestCase): self.mocks['request'].request = mock.Mock(side_effect=resp) def test_add_subscription(self): - """Valid subscription form""" + """Valid subscription form.""" subscription_list = { 'object': 'list', 'data': [], @@ -97,7 +98,7 @@ class GoldSubscriptionFormTests(TestCase): ]) def test_add_subscription_update_user(self): - """Valid subscription form""" + """Valid subscription form.""" subscription_list = { 'object': 'list', 'data': [], @@ -167,7 +168,7 @@ class GoldSubscriptionFormTests(TestCase): ]) def test_update_subscription_plan(self): - """Update subcription plan""" + """Update subcription plan.""" subscription_obj = { 'id': 'sub_12345', 'object': 'subscription', diff --git a/readthedocs/gold/tests/test_signals.py b/readthedocs/gold/tests/test_signals.py index 55103d155..f55e0b53b 100644 --- a/readthedocs/gold/tests/test_signals.py +++ b/readthedocs/gold/tests/test_signals.py @@ -1,13 +1,10 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture import mock from django.contrib.auth.models import User -from django.db.models.signals import pre_delete from django.test import TestCase -from readthedocs.projects.models import Project - from ..models import GoldUser -from ..signals import delete_customer class GoldSignalTests(TestCase): diff --git a/readthedocs/oauth/migrations/0003_move_github.py b/readthedocs/oauth/migrations/0003_move_github.py index 14998f5e9..2b4837f4e 100644 --- a/readthedocs/oauth/migrations/0003_move_github.py +++ b/readthedocs/oauth/migrations/0003_move_github.py @@ -3,7 +3,7 @@ import gc import json import logging -from django.db import migrations, models +from django.db import migrations log = logging.getLogger(__name__) @@ -23,7 +23,7 @@ def chunks(queryset, chunksize=1000): def forwards_move_repos(apps, schema_editor): - """Moves OAuth repos""" + """Moves OAuth repos.""" db = schema_editor.connection.alias # Organizations @@ -107,7 +107,7 @@ def forwards_move_repos(apps, schema_editor): else: new_repo.clone_url = data.get('clone_url') new_repo.json = json.dumps(data) - except (SyntaxError, ValueError) as e: + except (SyntaxError, ValueError): pass new_repo.save() log.info('Migrated project: %s', project.name) @@ -148,14 +148,14 @@ def forwards_move_repos(apps, schema_editor): new_repo.clone_url = clone_urls.get('ssh', project.git_url) else: new_repo.clone_url = clone_urls.get('https', project.html_url) - except (SyntaxError, ValueError) as e: + except (SyntaxError, ValueError): pass new_repo.save() log.info('Migrated project: %s', project.name) def reverse_move_repos(apps, schema_editor): - """Drop OAuth repos""" + """Drop OAuth repos.""" db = schema_editor.connection.alias RemoteRepository = apps.get_model('oauth', 'RemoteRepository') RemoteOrganization = apps.get_model('oauth', 'RemoteOrganization') diff --git a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py index 17bfef298..c79a997f1 100644 --- a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py +++ b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.db import migrations, models +from django.db import migrations def forwards_remove_content_types(apps, schema_editor): diff --git a/readthedocs/oauth/migrations/0006_move_oauth_source.py b/readthedocs/oauth/migrations/0006_move_oauth_source.py index 66d0db28f..341f7ef44 100644 --- a/readthedocs/oauth/migrations/0006_move_oauth_source.py +++ b/readthedocs/oauth/migrations/0006_move_oauth_source.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -from django.db import migrations, models +from django.db import migrations def forwards_move_repo_source(apps, schema_editor): - """Use source field to set repository account""" + """Use source field to set repository account.""" RemoteRepository = apps.get_model('oauth', 'RemoteRepository') SocialAccount = apps.get_model('socialaccount', 'SocialAccount') for account in SocialAccount.objects.all(): @@ -13,7 +13,7 @@ def forwards_move_repo_source(apps, schema_editor): def backwards_move_repo_source(apps, schema_editor): - RemoteRepository = apps.get_model('oauth', 'RemoteRepository') + apps.get_model('oauth', 'RemoteRepository') SocialAccount = apps.get_model('socialaccount', 'SocialAccount') for account in SocialAccount.objects.all(): rows = (account.remote_repositories @@ -21,7 +21,7 @@ def backwards_move_repo_source(apps, schema_editor): def forwards_move_org_source(apps, schema_editor): - """Use source field to set organization account""" + """Use source field to set organization account.""" RemoteOrganization = apps.get_model('oauth', 'RemoteOrganization') SocialAccount = apps.get_model('socialaccount', 'SocialAccount') for account in SocialAccount.objects.all(): @@ -31,8 +31,8 @@ def forwards_move_org_source(apps, schema_editor): def backwards_move_org_source(apps, schema_editor): - """Use source field to set organization account""" - RemoteOrganization = apps.get_model('oauth', 'RemoteOrganization') + """Use source field to set organization account.""" + apps.get_model('oauth', 'RemoteOrganization') SocialAccount = apps.get_model('socialaccount', 'SocialAccount') for account in SocialAccount.objects.all(): rows = (account.remote_organizations diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 95e7f3163..324e49864 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Django administration interface for `projects.models`""" from django.contrib import admin, messages @@ -102,9 +103,7 @@ class ProjectOwnerBannedFilter(admin.SimpleListFilter): OWNER_BANNED = 'true' def lookups(self, request, model_admin): - return ( - (self.OWNER_BANNED, _('Yes')), - ) + return ((self.OWNER_BANNED, _('Yes')),) def queryset(self, request, queryset): if self.value() == self.OWNER_BANNED: @@ -118,13 +117,15 @@ class ProjectAdmin(GuardedModelAdmin): prepopulated_fields = {'slug': ('name',)} list_display = ('name', 'slug', 'repo', 'repo_type', 'featured') - list_filter = ('repo_type', 'featured', 'privacy_level', - 'documentation_type', 'programming_language', - 'feature__feature_id', ProjectOwnerBannedFilter) + list_filter = ( + 'repo_type', 'featured', 'privacy_level', 'documentation_type', + 'programming_language', 'feature__feature_id', ProjectOwnerBannedFilter, + ) list_editable = ('featured',) search_fields = ('slug', 'repo') - inlines = [ProjectRelationshipInline, RedirectInline, - VersionInline, DomainInline] + inlines = [ + ProjectRelationshipInline, RedirectInline, VersionInline, DomainInline, + ] readonly_fields = ('feature_flags',) raw_id_fields = ('users', 'main_language_project') actions = ['send_owner_email', 'ban_owner'] @@ -151,18 +152,23 @@ class ProjectAdmin(GuardedModelAdmin): total = 0 for project in queryset: if project.users.count() == 1: - count = (UserProfile.objects - .filter(user__projects=project) - .update(banned=True)) + count = ( + UserProfile.objects + .filter(user__projects=project) + .update(banned=True) + ) # yapf: disabled total += count else: - messages.add_message(request, messages.ERROR, - 'Project has multiple owners: {}'.format(project)) + messages.add_message( + request, messages.ERROR, + 'Project has multiple owners: {}'.format(project), + ) if total == 0: messages.add_message(request, messages.ERROR, 'No users banned') else: - messages.add_message(request, messages.INFO, - 'Banned {} user(s)'.format(total)) + messages.add_message( + request, messages.INFO, 'Banned {} user(s)'.format(total), + ) ban_owner.short_description = 'Ban project owner' @@ -185,9 +191,8 @@ class ProjectAdmin(GuardedModelAdmin): def get_actions(self, request): actions = super().get_actions(request) actions['delete_selected'] = ( - self.__class__.delete_selected_and_artifacts, - 'delete_selected', - delete_selected.short_description + self.__class__.delete_selected_and_artifacts, 'delete_selected', + delete_selected.short_description, ) return actions diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 54a3e8916..85b439400 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -44,9 +44,7 @@ class RepositoryError(BuildEnvironmentError): 'You can not have two versions with the name latest or stable.', ) - FAILED_TO_CHECKOUT = _( - 'Failed to checkout revision: {}' - ) + FAILED_TO_CHECKOUT = _('Failed to checkout revision: {}') def get_default_message(self): if settings.ALLOW_PRIVATE_REPOS: diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index af68c7b8f..fe9e9d801 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -125,9 +125,7 @@ class ProjectBasicsForm(ProjectForm): _('Invalid project name, a project already exists with that name')) # yapf: disable # noqa if not potential_slug: # Check the generated slug won't be empty - raise forms.ValidationError( - _('Invalid project name'), - ) + raise forms.ValidationError(_('Invalid project name'),) return name @@ -181,7 +179,9 @@ class ProjectExtraForm(ProjectForm): for tag in tags: if len(tag) > 100: raise forms.ValidationError( - _('Length of each tag must be less than or equal to 100 characters.') + _( + 'Length of each tag must be less than or equal to 100 characters.' + ) ) return tags @@ -193,8 +193,10 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): python_interpreter = forms.ChoiceField( choices=constants.PYTHON_CHOICES, initial='python', - help_text=_('The Python interpreter used to create the virtual ' - 'environment.'), + help_text=_( + 'The Python interpreter used to create the virtual ' + 'environment.' + ), ) class Meta: @@ -231,9 +233,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): choices=[default_choice] + list(all_versions) ) - active_versions = self.instance.all_active_versions().values_list( - 'slug', 'verbose_name' - ) + active_versions = self.instance.all_active_versions().values_list('slug', 'verbose_name') # yapf: disabled self.fields['default_version'].widget = forms.Select( choices=active_versions ) @@ -249,6 +249,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, ProjectExtraForm): + class Meta: model = Project fields = ( @@ -322,14 +323,16 @@ class ProjectRelationshipBaseForm(forms.ModelForm): # This validation error is mostly for testing, users shouldn't see # this in normal circumstances raise forms.ValidationError( - _('Subproject nesting is not supported')) + _('Subproject nesting is not supported') + ) return self.project def clean_child(self): child = self.cleaned_data['child'] if child == self.project: raise forms.ValidationError( - _('A project can not be a subproject of itself')) + _('A project can not be a subproject of itself') + ) return child def get_subproject_queryset(self): @@ -448,8 +451,9 @@ def build_versions_form(project): class BaseUploadHTMLForm(forms.Form): content = forms.FileField(label=_('Zip file of HTML')) - overwrite = forms.BooleanField(required=False, - label=_('Overwrite existing HTML?')) + overwrite = forms.BooleanField( + required=False, label=_('Overwrite existing HTML?') + ) def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) @@ -500,7 +504,8 @@ class UserForm(forms.Form): user_qs = User.objects.filter(username=name) if not user_qs.exists(): raise forms.ValidationError( - _('User {name} does not exist').format(name=name)) + _('User {name} does not exist').format(name=name) + ) self.user = user_qs[0] return name @@ -523,7 +528,8 @@ class EmailHookForm(forms.Form): def clean_email(self): self.email = EmailHook.objects.get_or_create( - email=self.cleaned_data['email'], project=self.project)[0] + email=self.cleaned_data['email'], project=self.project + )[0] return self.email def save(self): @@ -541,7 +547,8 @@ class WebHookForm(forms.ModelForm): def save(self, commit=True): self.webhook = WebHook.objects.get_or_create( - url=self.cleaned_data['url'], project=self.project)[0] + url=self.cleaned_data['url'], project=self.project + )[0] self.project.webhook_notifications.add(self.webhook) return self.project @@ -589,21 +596,15 @@ class TranslationBaseForm(forms.Form): ) self.translation = project_translation_qs.first() if self.translation.language == self.parent.language: - msg = ( - 'Both projects can not have the same language ({lang}).' - ) + msg = ('Both projects can not have the same language ({lang}).') raise forms.ValidationError( _(msg).format(lang=self.parent.get_language_display()) ) exists_translation = ( - self.parent.translations - .filter(language=self.translation.language) - .exists() + self.parent.translations.filter(language=self.translation.language).exists() # yapf: disabled ) if exists_translation: - msg = ( - 'This project already has a translation for {lang}.' - ) + msg = ('This project already has a translation for {lang}.') raise forms.ValidationError( _(msg).format(lang=self.translation.get_language_display()) ) @@ -689,11 +690,10 @@ class DomainBaseForm(forms.ModelForm): def clean_canonical(self): canonical = self.cleaned_data['canonical'] _id = self.initial.get('id') - if canonical and Domain.objects.filter( - project=self.project, canonical=True - ).exclude(pk=_id).exists(): + if canonical and Domain.objects.filter(project=self.project, canonical=True).exclude(pk=_id).exists(): # yapf: disabled # noqa raise forms.ValidationError( - _('Only 1 Domain can be canonical at a time.')) + _('Only 1 Domain can be canonical at a time.') + ) return canonical @@ -796,7 +796,9 @@ class EnvironmentVariableForm(forms.ModelForm): ) elif self.project.environmentvariable_set.filter(name=name).exists(): raise forms.ValidationError( - _('There is already a variable with this name for this project'), + _( + 'There is already a variable with this name for this project' + ), ) elif ' ' in name: raise forms.ValidationError( diff --git a/readthedocs/projects/migrations/0007_migrate_canonical_data.py b/readthedocs/projects/migrations/0007_migrate_canonical_data.py index 79647aa8f..f1f8fe057 100644 --- a/readthedocs/projects/migrations/0007_migrate_canonical_data.py +++ b/readthedocs/projects/migrations/0007_migrate_canonical_data.py @@ -3,7 +3,7 @@ from django.db import migrations, transaction def migrate_canonical(apps, schema_editor): - Project = apps.get_model("projects", "Project") + Project = apps.get_model('projects', 'Project') for project in Project.objects.all(): if project.canonical_url: try: @@ -12,10 +12,10 @@ def migrate_canonical(apps, schema_editor): url=project.canonical_url, canonical=True, ) - print("Added {url} to {project}".format(url=project.canonical_url, project=project.name)) + print('Added {url} to {project}'.format(url=project.canonical_url, project=project.name)) except Exception as e: print(e) - print("Failed adding {url} to {project}".format( + print('Failed adding {url} to {project}'.format( url=project.canonical_url, project=project.name )) diff --git a/readthedocs/projects/migrations/0009_add_domain_field.py b/readthedocs/projects/migrations/0009_add_domain_field.py index 20703e89d..4c910c74e 100644 --- a/readthedocs/projects/migrations/0009_add_domain_field.py +++ b/readthedocs/projects/migrations/0009_add_domain_field.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import uuid import django.contrib.sites.models from django.db import migrations, models diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py index 301442333..bcdee33d1 100644 --- a/readthedocs/projects/migrations/0010_migrate_domain_data.py +++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py @@ -7,11 +7,11 @@ import readthedocs.projects.validators def migrate_url(apps, schema_editor): - Domain = apps.get_model("projects", "Domain") + Domain = apps.get_model('projects', 'Domain') Domain.objects.filter(count=0).delete() for domain in Domain.objects.all(): if domain.project.superprojects.count() or domain.project.main_language_project: - print("{project} is a subproject or translation. Deleting domain.".format( + print('{project} is a subproject or translation. Deleting domain.'.format( project=domain.project.slug)) domain.delete() continue @@ -23,10 +23,10 @@ def migrate_url(apps, schema_editor): try: domain.domain = domain_string domain.save() - print("Added {domain} from {url}".format(url=domain.url, domain=domain_string)) + print('Added {domain} from {url}'.format(url=domain.url, domain=domain_string)) except Exception as e: print(e) - print("Failed {domain} from {url}".format(url=domain.url, domain=domain_string)) + print('Failed {domain} from {url}'.format(url=domain.url, domain=domain_string)) dms = Domain.objects.filter(domain=domain_string).order_by('-count') if dms.count() > 1: diff --git a/readthedocs/projects/migrations/0011_delete-url.py b/readthedocs/projects/migrations/0011_delete-url.py index caa9c612d..3b01ed32c 100644 --- a/readthedocs/projects/migrations/0011_delete-url.py +++ b/readthedocs/projects/migrations/0011_delete-url.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0016_build-queue-name.py b/readthedocs/projects/migrations/0016_build-queue-name.py index 6baa0a922..0350bae86 100644 --- a/readthedocs/projects/migrations/0016_build-queue-name.py +++ b/readthedocs/projects/migrations/0016_build-queue-name.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -from django.db import migrations, models +from django.db import migrations def update_build_queue(apps, schema): - """Update project build queue to include the previously implied build- prefix""" - Project = apps.get_model("projects", "Project") + """Update project build queue to include the previously implied build- + prefix.""" + Project = apps.get_model('projects', 'Project') for project in Project.objects.all(): if project.build_queue is not None: if not project.build_queue.startswith('build-'): diff --git a/readthedocs/projects/migrations/0020_add-api-project-proxy.py b/readthedocs/projects/migrations/0020_add-api-project-proxy.py index 9e1c8458b..0040581f7 100644 --- a/readthedocs/projects/migrations/0020_add-api-project-proxy.py +++ b/readthedocs/projects/migrations/0020_add-api-project-proxy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.12 on 2017-10-27 12:56 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py index a3572c84c..0b14b4bd7 100644 --- a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py +++ b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Add feature for allowing access to deprecated webhook endpoints""" +"""Add feature for allowing access to deprecated webhook endpoints.""" from django.db import migrations diff --git a/readthedocs/projects/migrations/0023_migrate-alias-slug.py b/readthedocs/projects/migrations/0023_migrate-alias-slug.py index c86ea6e07..c38907700 100644 --- a/readthedocs/projects/migrations/0023_migrate-alias-slug.py +++ b/readthedocs/projects/migrations/0023_migrate-alias-slug.py @@ -12,7 +12,7 @@ class Migration(migrations.Migration): # so that we don't break a bunch of folks URL's. # They will have to change them on update. invalid_chars_re = re.compile('[^-._a-zA-Z0-9]') - ProjectRelationship = apps.get_model("projects", "ProjectRelationship") + ProjectRelationship = apps.get_model('projects', 'ProjectRelationship') for p in ProjectRelationship.objects.all(): if p.alias and invalid_chars_re.match(p.alias): new_alias = invalid_chars_re.sub('', p.alias) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index d3ae9b67a..ae3cbdc0e 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project models.""" import fnmatch @@ -51,11 +52,15 @@ class ProjectRelationship(models.Model): This is used for subprojects """ - parent = models.ForeignKey('Project', verbose_name=_('Parent'), - related_name='subprojects') - child = models.ForeignKey('Project', verbose_name=_('Child'), - related_name='superprojects') - alias = models.SlugField(_('Alias'), max_length=255, null=True, blank=True, db_index=False) + parent = models.ForeignKey( + 'Project', verbose_name=_('Parent'), related_name='subprojects', + ) + child = models.ForeignKey( + 'Project', verbose_name=_('Child'), related_name='superprojects', + ) + alias = models.SlugField( + _('Alias'), max_length=255, null=True, blank=True, db_index=False, + ) objects = ChildRelatedProjectQuerySet.as_manager() @@ -82,76 +87,105 @@ class Project(models.Model): modified_date = models.DateTimeField(_('Modified date'), auto_now=True) # Generally from conf.py - users = models.ManyToManyField(User, verbose_name=_('User'), - related_name='projects') + users = models.ManyToManyField( + User, verbose_name=_('User'), related_name='projects' + ) # A DNS label can contain up to 63 characters. name = models.CharField(_('Name'), max_length=63) slug = models.SlugField(_('Slug'), max_length=63, unique=True) - description = models.TextField(_('Description'), blank=True, - help_text=_('The reStructuredText ' - 'description of the project')) - repo = models.CharField(_('Repository URL'), max_length=255, - validators=[validate_repository_url], - help_text=_('Hosted documentation repository URL')) - repo_type = models.CharField(_('Repository type'), max_length=10, - choices=constants.REPO_CHOICES, default='git') - project_url = models.URLField(_('Project homepage'), blank=True, - help_text=_('The project\'s homepage')) - canonical_url = models.URLField(_('Canonical URL'), blank=True, - help_text=_('URL that documentation is expected to serve from')) + description = models.TextField( + _('Description'), blank=True, + help_text=_('The reStructuredText ' + 'description of the project') + ) + repo = models.CharField( + _('Repository URL'), max_length=255, + validators=[validate_repository_url], + help_text=_('Hosted documentation repository URL') + ) + repo_type = models.CharField( + _('Repository type'), max_length=10, choices=constants.REPO_CHOICES, + default='git' + ) + project_url = models.URLField( + _('Project homepage'), blank=True, + help_text=_('The project\'s homepage') + ) + canonical_url = models.URLField( + _('Canonical URL'), blank=True, + help_text=_('URL that documentation is expected to serve from') + ) single_version = models.BooleanField( - _('Single version'), default=False, - help_text=_('A single version site has no translations and only your ' - '"latest" version, served at the root of the domain. Use ' - 'this with caution, only turn it on if you will never ' - 'have multiple versions of your docs.')) + _('Single version'), default=False, help_text=_( + 'A single version site has no translations and only your ' + '"latest" version, served at the root of the domain. Use ' + 'this with caution, only turn it on if you will never ' + 'have multiple versions of your docs.' + ) + ) default_version = models.CharField( _('Default version'), max_length=255, default=LATEST, - help_text=_('The version of your project that / redirects to')) + help_text=_('The version of your project that / redirects to') + ) # In default_branch, None means the backend should choose the # appropriate branch. Eg 'master' for git default_branch = models.CharField( _('Default branch'), max_length=255, default=None, null=True, - blank=True, help_text=_('What branch "latest" points to. Leave empty ' - 'to use the default value for your VCS (eg. ' - 'trunk or master).')) + blank=True, help_text=_( + 'What branch "latest" points to. Leave empty ' + 'to use the default value for your VCS (eg. ' + 'trunk or master).' + ) + ) requirements_file = models.CharField( _('Requirements file'), max_length=255, default=None, null=True, blank=True, help_text=_( 'A ' 'pip requirements file needed to build your documentation. ' - 'Path from the root of your project.')) + 'Path from the root of your project.' + ) + ) documentation_type = models.CharField( _('Documentation type'), max_length=20, - choices=constants.DOCUMENTATION_CHOICES, default='sphinx', - help_text=_('Type of documentation you are building. More info.')) + choices=constants.DOCUMENTATION_CHOICES, default='sphinx', help_text=_( + 'Type of documentation you are building. More info.' + ) + ) # Project features cdn_enabled = models.BooleanField(_('CDN Enabled'), default=False) analytics_code = models.CharField( - _('Analytics code'), max_length=50, null=True, blank=True, - help_text=_('Google Analytics Tracking ID ' - '(ex. UA-22345342-1). ' - 'This may slow down your page loads.')) + _('Analytics code'), max_length=50, null=True, blank=True, help_text=_( + 'Google Analytics Tracking ID ' + '(ex. UA-22345342-1). ' + 'This may slow down your page loads.' + ) + ) container_image = models.CharField( - _('Alternative container image'), max_length=64, null=True, blank=True) + _('Alternative container image'), max_length=64, null=True, blank=True + ) container_mem_limit = models.CharField( _('Container memory limit'), max_length=10, null=True, blank=True, - help_text=_('Memory limit in Docker format ' - '-- example: 512m or 1g')) + help_text=_( + 'Memory limit in Docker format ' + '-- example: 512m or 1g' + ) + ) container_time_limit = models.IntegerField( _('Container time limit in seconds'), null=True, blank=True, ) build_queue = models.CharField( - _('Alternate build queue id'), max_length=32, null=True, blank=True) + _('Alternate build queue id'), max_length=32, null=True, blank=True + ) allow_promos = models.BooleanField( - _('Allow paid advertising'), default=True, help_text=_( - 'If unchecked, users will still see community ads.')) + _('Allow paid advertising'), default=True, + help_text=_('If unchecked, users will still see community ads.') + ) ad_free = models.BooleanField( _('Ad-free'), default=False, @@ -164,88 +198,103 @@ class Project(models.Model): # Sphinx specific build options. enable_epub_build = models.BooleanField( - _('Enable EPUB build'), default=True, - help_text=_( - 'Create a EPUB version of your documentation with each build.')) + _('Enable EPUB build'), default=True, help_text=_( + 'Create a EPUB version of your documentation with each build.' + ) + ) enable_pdf_build = models.BooleanField( - _('Enable PDF build'), default=True, - help_text=_( - 'Create a PDF version of your documentation with each build.')) + _('Enable PDF build'), default=True, help_text=_( + 'Create a PDF version of your documentation with each build.' + ) + ) # Other model data. - path = models.CharField(_('Path'), max_length=255, editable=False, - help_text=_('The directory where ' - 'conf.py lives')) + path = models.CharField( + _('Path'), max_length=255, editable=False, + help_text=_('The directory where ' + 'conf.py lives') + ) conf_py_file = models.CharField( _('Python configuration file'), max_length=255, default='', blank=True, - help_text=_('Path from project root to conf.py file ' - '(ex. docs/conf.py). ' - 'Leave blank if you want us to find it for you.')) + help_text=_( + 'Path from project root to conf.py file ' + '(ex. docs/conf.py). ' + 'Leave blank if you want us to find it for you.' + ) + ) featured = models.BooleanField(_('Featured'), default=False) skip = models.BooleanField(_('Skip'), default=False) install_project = models.BooleanField( - _('Install Project'), - help_text=_('Install your project inside a virtualenv using setup.py ' - 'install'), - default=False + _('Install Project'), help_text=_( + 'Install your project inside a virtualenv using setup.py ' + 'install' + ), default=False ) # This model attribute holds the python interpreter used to create the # virtual environment python_interpreter = models.CharField( - _('Python Interpreter'), - max_length=20, - choices=constants.PYTHON_CHOICES, - default='python', - help_text=_('The Python interpreter used to create the virtual ' - 'environment.')) + _('Python Interpreter'), max_length=20, + choices=constants.PYTHON_CHOICES, default='python', help_text=_( + 'The Python interpreter used to create the virtual ' + 'environment.' + ) + ) use_system_packages = models.BooleanField( - _('Use system packages'), - help_text=_('Give the virtual environment access to the global ' - 'site-packages dir.'), - default=False + _('Use system packages'), help_text=_( + 'Give the virtual environment access to the global ' + 'site-packages dir.' + ), default=False ) privacy_level = models.CharField( _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, - default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public'), - help_text=_('Level of privacy that you want on the repository. ' - 'Protected means public but not in listings.')) + default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', + 'public'), help_text=_( + 'Level of privacy that you want on the repository. ' + 'Protected means public but not in listings.' + ) + ) version_privacy_level = models.CharField( _('Version Privacy Level'), max_length=20, - choices=constants.PRIVACY_CHOICES, default=getattr( - settings, 'DEFAULT_PRIVACY_LEVEL', 'public'), - help_text=_('Default level of privacy you want on built ' - 'versions of documentation.')) + choices=constants.PRIVACY_CHOICES, + default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', + 'public'), help_text=_( + 'Default level of privacy you want on built ' + 'versions of documentation.' + ) + ) # Subprojects related_projects = models.ManyToManyField( 'self', verbose_name=_('Related projects'), blank=True, - symmetrical=False, through=ProjectRelationship) + symmetrical=False, through=ProjectRelationship + ) # Language bits - language = models.CharField(_('Language'), max_length=20, default='en', - help_text=_('The language the project ' - 'documentation is rendered in. ' - "Note: this affects your project's URL."), - choices=constants.LANGUAGES) + language = models.CharField( + _('Language'), max_length=20, default='en', help_text=_( + 'The language the project ' + 'documentation is rendered in. ' + "Note: this affects your project's URL." + ), choices=constants.LANGUAGES + ) programming_language = models.CharField( - _('Programming Language'), - max_length=20, - default='words', - help_text=_( - 'The primary programming language the project is written in.'), - choices=constants.PROGRAMMING_LANGUAGES, blank=True) + _('Programming Language'), max_length=20, default='words', help_text=_( + 'The primary programming language the project is written in.' + ), choices=constants.PROGRAMMING_LANGUAGES, blank=True + ) # A subproject pointed at its main language, so it can be tracked - main_language_project = models.ForeignKey('self', - related_name='translations', - on_delete=models.SET_NULL, - blank=True, null=True) + main_language_project = models.ForeignKey( + 'self', related_name='translations', on_delete=models.SET_NULL, + blank=True, null=True + ) has_valid_webhook = models.BooleanField( - default=False, help_text=_('This project has been built with a webhook') + default=False, + help_text=_('This project has been built with a webhook') ) has_valid_clone = models.BooleanField( default=False, help_text=_('This project has been successfully cloned') @@ -313,7 +362,10 @@ class Project(models.Model): try: if not first_save: broadcast( - type='app', task=tasks.update_static_metadata, args=[self.pk],) + type='app', + task=tasks.update_static_metadata, + args=[self.pk], + ) except Exception: log.exception('failed to update static metadata') try: @@ -332,12 +384,17 @@ class Project(models.Model): Always use http for now, to avoid content warnings. """ - return resolve(project=self, version_slug=version_slug, language=lang_slug, private=private) + return resolve( + project=self, version_slug=version_slug, language=lang_slug, + private=private + ) def get_builds_url(self): - return reverse('builds_project_list', kwargs={ - 'project_slug': self.slug, - }) + return reverse( + 'builds_project_list', kwargs={ + 'project_slug': self.slug, + } + ) def get_canonical_url(self): if getattr(settings, 'DONT_HIT_DB', True): @@ -402,11 +459,14 @@ class Project(models.Model): def get_downloads(self): downloads = {} downloads['htmlzip'] = self.get_production_media_url( - 'htmlzip', self.get_default_version()) + 'htmlzip', self.get_default_version() + ) downloads['epub'] = self.get_production_media_url( - 'epub', self.get_default_version()) + 'epub', self.get_default_version() + ) downloads['pdf'] = self.get_production_media_url( - 'pdf', self.get_default_version()) + 'pdf', self.get_default_version() + ) return downloads @property @@ -506,7 +566,9 @@ class Project(models.Model): """Find a ``conf.py`` file in the project checkout.""" if self.conf_py_file: conf_path = os.path.join( - self.checkout_path(version), self.conf_py_file,) + self.checkout_path(version), + self.conf_py_file, + ) if os.path.exists(conf_path): log.info('Inserting conf.py file path from model') @@ -532,9 +594,7 @@ class Project(models.Model): ProjectConfigurationError.MULTIPLE_CONF_FILES ) - raise ProjectConfigurationError( - ProjectConfigurationError.NOT_FOUND - ) + raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) def conf_dir(self, version=LATEST): conf_file = self.conf_file(version) @@ -671,8 +731,10 @@ class Project(models.Model): def active_versions(self): from readthedocs.builds.models import Version versions = Version.objects.public(project=self, only_active=True) - return (versions.filter(built=True, active=True) | - versions.filter(active=True, uploaded=True)) + return ( + versions.filter(built=True, active=True) | + versions.filter(active=True, uploaded=True) + ) def ordered_active_versions(self, user=None): from readthedocs.builds.models import Version @@ -713,23 +775,25 @@ class Project(models.Model): current_stable = self.get_stable_version() if current_stable: identifier_updated = ( - new_stable.identifier != current_stable.identifier) + new_stable.identifier != current_stable.identifier + ) if identifier_updated and current_stable.active and current_stable.machine: log.info( 'Update stable version: {project}:{version}'.format( - project=self.slug, - version=new_stable.identifier)) + project=self.slug, version=new_stable.identifier + ) + ) current_stable.identifier = new_stable.identifier current_stable.save() return new_stable else: log.info( - 'Creating new stable version: {project}:{version}'.format( - project=self.slug, - version=new_stable.identifier)) + 'Creating new stable version: {project}:{version}' + .format(project=self.slug, version=new_stable.identifier) + ) current_stable = self.versions.create_stable( - type=new_stable.type, - identifier=new_stable.identifier) + type=new_stable.type, identifier=new_stable.identifier + ) return new_stable def versions_from_branch_name(self, branch): @@ -766,7 +830,9 @@ class Project(models.Model): def add_subproject(self, child, alias=None): subproject, __ = ProjectRelationship.objects.get_or_create( - parent=self, child=child, alias=alias, + parent=self, + child=child, + alias=alias, ) return subproject @@ -799,7 +865,7 @@ class Project(models.Model): @property def show_advertising(self): """ - Whether this project is ad-free + Whether this project is ad-free. :returns: ``True`` if advertising should be shown and ``False`` otherwise :rtype: bool @@ -887,10 +953,13 @@ class ImportedFile(models.Model): things like CDN invalidation. """ - project = models.ForeignKey('Project', verbose_name=_('Project'), - related_name='imported_files') - version = models.ForeignKey('builds.Version', verbose_name=_('Version'), - related_name='imported_files', null=True) + project = models.ForeignKey( + 'Project', verbose_name=_('Project'), related_name='imported_files' + ) + version = models.ForeignKey( + 'builds.Version', verbose_name=_('Version'), + related_name='imported_files', null=True + ) name = models.CharField(_('Name'), max_length=255) slug = models.SlugField(_('Slug')) path = models.CharField(_('Path'), max_length=255) @@ -899,15 +968,17 @@ class ImportedFile(models.Model): modified_date = models.DateTimeField(_('Modified date'), auto_now=True) def get_absolute_url(self): - return resolve(project=self.project, version_slug=self.version.slug, filename=self.path) + return resolve( + project=self.project, version_slug=self.version.slug, + filename=self.path + ) def __str__(self): return '{}: {}'.format(self.name, self.project) class Notification(models.Model): - project = models.ForeignKey(Project, - related_name='%(class)s_notifications') + project = models.ForeignKey(Project, related_name='%(class)s_notifications') objects = RelatedProjectQuerySet.as_manager() class Meta: @@ -924,8 +995,9 @@ class EmailHook(Notification): @python_2_unicode_compatible class WebHook(Notification): - url = models.URLField(max_length=600, blank=True, - help_text=_('URL to send the webhook to')) + url = models.URLField( + max_length=600, blank=True, help_text=_('URL to send the webhook to') + ) def __str__(self): return self.url @@ -937,8 +1009,10 @@ class Domain(models.Model): """A custom domain name for a project.""" project = models.ForeignKey(Project, related_name='domains') - domain = models.CharField(_('Domain'), unique=True, max_length=255, - validators=[validate_domain_name]) + domain = models.CharField( + _('Domain'), unique=True, max_length=255, + validators=[validate_domain_name] + ) machine = models.BooleanField( default=False, help_text=_('This Domain was auto-created') ) @@ -946,18 +1020,19 @@ class Domain(models.Model): default=False, help_text=_('This Domain is a CNAME for the project') ) canonical = models.BooleanField( - default=False, - help_text=_( + default=False, help_text=_( 'This Domain is the primary one where the documentation is ' - 'served from') + 'served from' + ) ) https = models.BooleanField( - _('Use HTTPS'), - default=False, + _('Use HTTPS'), default=False, help_text=_('Always use HTTPS for this domain') ) - count = models.IntegerField(default=0, help_text=_( - 'Number of times this domain has been hit'),) + count = models.IntegerField( + default=0, + help_text=_('Number of times this domain has been hit'), + ) objects = RelatedProjectQuerySet.as_manager() @@ -965,7 +1040,9 @@ class Domain(models.Model): ordering = ('-canonical', '-machine', 'domain') def __str__(self): - return '{domain} pointed at {project}'.format(domain=self.domain, project=self.project.name) + return '{domain} pointed at {project}'.format( + domain=self.domain, project=self.project.name + ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ from readthedocs.projects import tasks @@ -975,13 +1052,19 @@ class Domain(models.Model): else: self.domain = parsed.path super().save(*args, **kwargs) - broadcast(type='app', task=tasks.symlink_domain, - args=[self.project.pk, self.pk],) + broadcast( + type='app', + task=tasks.symlink_domain, + args=[self.project.pk, self.pk], + ) def delete(self, *args, **kwargs): # pylint: disable=arguments-differ from readthedocs.projects import tasks - broadcast(type='app', task=tasks.symlink_domain, - args=[self.project.pk, self.pk, True],) + broadcast( + type='app', + task=tasks.symlink_domain, + args=[self.project.pk, self.pk, True], + ) super().delete(*args, **kwargs) @@ -1055,9 +1138,7 @@ class Feature(models.Model): objects = FeatureQuerySet.as_manager() def __str__(self): - return '{} feature'.format( - self.get_feature_display(), - ) + return '{} feature'.format(self.get_feature_display(),) def get_feature_display(self): """ diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index ddf6033a5..4848b575f 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- -"""Project notifications""" -from datetime import timedelta +"""Project notifications.""" from django.http import HttpRequest -from django.utils import timezone -from messages_extends.models import Message from readthedocs.notifications import Notification from readthedocs.notifications.constants import REQUIREMENT diff --git a/readthedocs/projects/signals.py b/readthedocs/projects/signals.py index 513150b11..1d1788ef2 100644 --- a/readthedocs/projects/signals.py +++ b/readthedocs/projects/signals.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- -"""Project signals""" + +"""Project signals.""" import django.dispatch -before_vcs = django.dispatch.Signal(providing_args=["version"]) -after_vcs = django.dispatch.Signal(providing_args=["version"]) +before_vcs = django.dispatch.Signal(providing_args=['version']) +after_vcs = django.dispatch.Signal(providing_args=['version']) -before_build = django.dispatch.Signal(providing_args=["version"]) -after_build = django.dispatch.Signal(providing_args=["version"]) +before_build = django.dispatch.Signal(providing_args=['version']) +after_build = django.dispatch.Signal(providing_args=['version']) -project_import = django.dispatch.Signal(providing_args=["project"]) +project_import = django.dispatch.Signal(providing_args=['project']) -files_changed = django.dispatch.Signal(providing_args=["project", "files"]) +files_changed = django.dispatch.Signal(providing_args=['project', 'files']) # Used to force verify a domain (eg. for SSL cert issuance) -domain_verify = django.dispatch.Signal(providing_args=["domain"]) +domain_verify = django.dispatch.Signal(providing_args=['domain']) diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index b9af472d9..95ec9dbbe 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -1447,7 +1447,7 @@ def finish_inactive_builds(): @app.task(queue='web') def retry_domain_verification(domain_pk): """ - Trigger domain verification on a domain + Trigger domain verification on a domain. :param domain_pk: a `Domain` pk to verify """ diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index f0c035891..eebec75c1 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Project URLs for authenticated users.""" from django.conf.urls import url @@ -29,176 +30,204 @@ from readthedocs.projects.views.private import ( urlpatterns = [ - url(r'^$', - ProjectDashboard.as_view(), - name='projects_dashboard'), - - url(r'^import/$', - ImportView.as_view(wizard_class=ImportWizardView), - {'wizard': ImportWizardView}, - name='projects_import'), - - url(r'^import/manual/$', - ImportWizardView.as_view(), - name='projects_import_manual'), - - url(r'^import/manual/demo/$', - ImportDemoView.as_view(), - name='projects_import_demo'), - - url(r'^(?P[-\w]+)/$', - private.project_manage, - name='projects_manage'), - - url(r'^(?P[-\w]+)/edit/$', - ProjectUpdate.as_view(), - name='projects_edit'), - - url(r'^(?P[-\w]+)/advanced/$', - ProjectAdvancedUpdate.as_view(), - name='projects_advanced'), - - url(r'^(?P[-\w]+)/version/(?P[^/]+)/delete_html/$', - private.project_version_delete_html, - name='project_version_delete_html'), - - url(r'^(?P[-\w]+)/version/(?P[^/]+)/$', - private.project_version_detail, - name='project_version_detail'), - - url(r'^(?P[-\w]+)/versions/$', - private.project_versions, - name='projects_versions'), - - url(r'^(?P[-\w]+)/delete/$', - private.project_delete, - name='projects_delete'), - - url(r'^(?P[-\w]+)/users/$', - private.project_users, - name='projects_users'), - - url(r'^(?P[-\w]+)/users/delete/$', - private.project_users_delete, - name='projects_users_delete'), - - url(r'^(?P[-\w]+)/notifications/$', - private.project_notifications, - name='projects_notifications'), - - url(r'^(?P[-\w]+)/notifications/delete/$', - private.project_notifications_delete, - name='projects_notification_delete'), - - url(r'^(?P[-\w]+)/translations/$', - private.project_translations, - name='projects_translations'), - - url(r'^(?P[-\w]+)/translations/delete/(?P[-\w]+)/$', # noqa + url(r'^$', ProjectDashboard.as_view(), name='projects_dashboard'), + url( + r'^import/$', ImportView.as_view(wizard_class=ImportWizardView), + {'wizard': ImportWizardView}, name='projects_import' + ), + url( + r'^import/manual/$', ImportWizardView.as_view(), + name='projects_import_manual' + ), + url( + r'^import/manual/demo/$', ImportDemoView.as_view(), + name='projects_import_demo' + ), + url( + r'^(?P[-\w]+)/$', private.project_manage, + name='projects_manage' + ), + url( + r'^(?P[-\w]+)/edit/$', ProjectUpdate.as_view(), + name='projects_edit' + ), + url( + r'^(?P[-\w]+)/advanced/$', + ProjectAdvancedUpdate.as_view(), name='projects_advanced' + ), + url( + r'^(?P[-\w]+)/version/(?P[^/]+)/delete_html/$', + private.project_version_delete_html, name='project_version_delete_html' + ), + url( + r'^(?P[-\w]+)/version/(?P[^/]+)/$', + private.project_version_detail, name='project_version_detail' + ), + url( + r'^(?P[-\w]+)/versions/$', private.project_versions, + name='projects_versions' + ), + url( + r'^(?P[-\w]+)/delete/$', private.project_delete, + name='projects_delete' + ), + url( + r'^(?P[-\w]+)/users/$', private.project_users, + name='projects_users' + ), + url( + r'^(?P[-\w]+)/users/delete/$', + private.project_users_delete, name='projects_users_delete' + ), + url( + r'^(?P[-\w]+)/notifications/$', + private.project_notifications, name='projects_notifications' + ), + url( + r'^(?P[-\w]+)/notifications/delete/$', + private.project_notifications_delete, name='projects_notification_delete' + ), + url( + r'^(?P[-\w]+)/translations/$', + private.project_translations, name='projects_translations' + ), + url( + r'^(?P[-\w]+)/translations/delete/(?P[-\w]+)/$', # noqa private.project_translations_delete, - name='projects_translations_delete'), - - url(r'^(?P[-\w]+)/redirects/$', - private.project_redirects, - name='projects_redirects'), - - url(r'^(?P[-\w]+)/redirects/delete/$', - private.project_redirects_delete, - name='projects_redirects_delete'), - - url(r'^(?P[-\w]+)/advertising/$', - ProjectAdvertisingUpdate.as_view(), - name='projects_advertising'), + name='projects_translations_delete' + ), + url( + r'^(?P[-\w]+)/redirects/$', private.project_redirects, + name='projects_redirects' + ), + url( + r'^(?P[-\w]+)/redirects/delete/$', + private.project_redirects_delete, name='projects_redirects_delete' + ), + url( + r'^(?P[-\w]+)/advertising/$', + ProjectAdvertisingUpdate.as_view(), name='projects_advertising' + ), ] domain_urls = [ - url(r'^(?P[-\w]+)/domains/$', - DomainList.as_view(), - name='projects_domains'), - url(r'^(?P[-\w]+)/domains/create/$', - DomainCreate.as_view(), - name='projects_domains_create'), - url(r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$', - DomainUpdate.as_view(), - name='projects_domains_edit'), - url(r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$', - DomainDelete.as_view(), - name='projects_domains_delete'), + url( + r'^(?P[-\w]+)/domains/$', DomainList.as_view(), + name='projects_domains' + ), + url( + r'^(?P[-\w]+)/domains/create/$', DomainCreate.as_view(), + name='projects_domains_create' + ), + url( + r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$', + DomainUpdate.as_view(), name='projects_domains_edit' + ), + url( + r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$', + DomainDelete.as_view(), name='projects_domains_delete' + ), ] urlpatterns += domain_urls integration_urls = [ - url(r'^(?P{project_slug})/integrations/$'.format(**pattern_opts), - IntegrationList.as_view(), - name='projects_integrations'), - url(r'^(?P{project_slug})/integrations/sync/$'.format(**pattern_opts), - IntegrationWebhookSync.as_view(), - name='projects_integrations_webhooks_sync'), - url((r'^(?P{project_slug})/integrations/create/$' - .format(**pattern_opts)), - IntegrationCreate.as_view(), - name='projects_integrations_create'), - url((r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/$' - .format(**pattern_opts)), - IntegrationDetail.as_view(), - name='projects_integrations_detail'), - url((r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/' - r'exchange/(?P[-\w]+)/$' - .format(**pattern_opts)), - IntegrationExchangeDetail.as_view(), + url( + r'^(?P{project_slug})/integrations/$'.format( + **pattern_opts + ), IntegrationList.as_view(), name='projects_integrations' + ), + url( + r'^(?P{project_slug})/integrations/sync/$'.format( + **pattern_opts + ), IntegrationWebhookSync.as_view(), + name='projects_integrations_webhooks_sync' + ), + url(( + r'^(?P{project_slug})/integrations/create/$'.format( + **pattern_opts + ) + ), IntegrationCreate.as_view(), name='projects_integrations_create'), + url(( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/$'.format( + **pattern_opts + ) + ), IntegrationDetail.as_view(), name='projects_integrations_detail'), + url(( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/' + r'exchange/(?P[-\w]+)/$'.format(**pattern_opts) + ), IntegrationExchangeDetail.as_view(), name='projects_integrations_exchanges_detail'), - url((r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/sync/$' - .format(**pattern_opts)), - IntegrationWebhookSync.as_view(), + url(( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/sync/$'.format( + **pattern_opts + ) + ), IntegrationWebhookSync.as_view(), name='projects_integrations_webhooks_sync'), - url((r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/delete/$' - .format(**pattern_opts)), - IntegrationDelete.as_view(), - name='projects_integrations_delete'), + url(( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/delete/$'.format( + **pattern_opts + ) + ), IntegrationDelete.as_view(), name='projects_integrations_delete'), ] urlpatterns += integration_urls subproject_urls = [ - url(r'^(?P{project_slug})/subprojects/$'.format(**pattern_opts), - private.ProjectRelationshipList.as_view(), - name='projects_subprojects'), - url((r'^(?P{project_slug})/subprojects/create/$' - .format(**pattern_opts)), - private.ProjectRelationshipCreate.as_view(), + url( + r'^(?P{project_slug})/subprojects/$'.format( + **pattern_opts + ), private.ProjectRelationshipList.as_view(), + name='projects_subprojects' + ), + url(( + r'^(?P{project_slug})/subprojects/create/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipCreate.as_view(), name='projects_subprojects_create'), - url((r'^(?P{project_slug})/' - r'subprojects/(?P{project_slug})/edit/$' - .format(**pattern_opts)), - private.ProjectRelationshipUpdate.as_view(), + url(( + r'^(?P{project_slug})/' + r'subprojects/(?P{project_slug})/edit/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipUpdate.as_view(), name='projects_subprojects_update'), - url((r'^(?P{project_slug})/' - r'subprojects/(?P{project_slug})/delete/$' - .format(**pattern_opts)), - private.ProjectRelationshipDelete.as_view(), + url(( + r'^(?P{project_slug})/' + r'subprojects/(?P{project_slug})/delete/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipDelete.as_view(), name='projects_subprojects_delete'), ] urlpatterns += subproject_urls environmentvariable_urls = [ - url(r'^(?P[-\w]+)/environmentvariables/$', - EnvironmentVariableList.as_view(), - name='projects_environmentvariables'), - url(r'^(?P[-\w]+)/environmentvariables/create/$', + url( + r'^(?P[-\w]+)/environmentvariables/$', + EnvironmentVariableList.as_view(), name='projects_environmentvariables' + ), + url( + r'^(?P[-\w]+)/environmentvariables/create/$', EnvironmentVariableCreate.as_view(), - name='projects_environmentvariables_create'), - url(r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/$', + name='projects_environmentvariables_create' + ), + url( + r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/$', EnvironmentVariableDetail.as_view(), - name='projects_environmentvariables_detail'), - url(r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/delete/$', + name='projects_environmentvariables_detail' + ), + url( + r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/delete/$', EnvironmentVariableDelete.as_view(), - name='projects_environmentvariables_delete'), + name='projects_environmentvariables_delete' + ), ] urlpatterns += environmentvariable_urls diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 87076f450..aa85f2d64 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -220,9 +220,7 @@ def project_delete(request, project_slug): if request.method == 'POST': broadcast( - type='app', - task=tasks.remove_dirs, - args=[(project.doc_path,)] + type='app', task=tasks.remove_dirs, args=[(project.doc_path,)] ) project.delete() messages.success(request, _('Project deleted')) @@ -725,6 +723,7 @@ class DomainMixin(ProjectAdminMixin, PrivateViewMixin): class DomainList(DomainMixin, ListViewWithForm): + def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py index 270fe4828..123e8b7f7 100644 --- a/readthedocs/rtd_tests/base.py +++ b/readthedocs/rtd_tests/base.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Base classes and mixins for unit tests.""" import logging import os @@ -21,7 +22,7 @@ class RTDTestCase(TestCase): self.original_DOCROOT = settings.DOCROOT self.cwd = os.path.dirname(__file__) self.build_dir = tempfile.mkdtemp() - log.info("build dir: %s", self.build_dir) + log.info('build dir: %s', self.build_dir) if not os.path.exists(self.build_dir): os.makedirs(self.build_dir) settings.DOCROOT = self.build_dir diff --git a/readthedocs/rtd_tests/files/api.fjson b/readthedocs/rtd_tests/files/api.fjson index 0e6077c56..7972e09c8 100644 --- a/readthedocs/rtd_tests/files/api.fjson +++ b/readthedocs/rtd_tests/files/api.fjson @@ -43,4 +43,4 @@ "title": "Internationalization" }, "metatags": "" -} \ No newline at end of file +} diff --git a/readthedocs/rtd_tests/fixtures/sample_repo/source/index.rst b/readthedocs/rtd_tests/fixtures/sample_repo/source/index.rst index d86e67de5..164c84a69 100644 --- a/readthedocs/rtd_tests/fixtures/sample_repo/source/index.rst +++ b/readthedocs/rtd_tests/fixtures/sample_repo/source/index.rst @@ -17,4 +17,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/readthedocs/rtd_tests/fixtures/spec/v2/schema.yml b/readthedocs/rtd_tests/fixtures/spec/v2/schema.yml index add1eeafe..3ff7b7fb7 100644 --- a/readthedocs/rtd_tests/fixtures/spec/v2/schema.yml +++ b/readthedocs/rtd_tests/fixtures/spec/v2/schema.yml @@ -123,7 +123,7 @@ submodules: # List of submodules to be ignored # Default: [] exclude: any(list(str()), enum('all'), required=False) - + # Do a recursive clone? # Default: false recursive: bool(required=False) diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py index 0d3ef1a56..c848fedd6 100644 --- a/readthedocs/rtd_tests/mocks/environment.py +++ b/readthedocs/rtd_tests/mocks/environment.py @@ -1,10 +1,11 @@ +# -*- coding: utf-8 -*- # pylint: disable=missing-docstring import mock class EnvironmentMockGroup: - """Mock out necessary environment pieces""" + """Mock out necessary environment pieces.""" def __init__(self): self.patches = { @@ -58,7 +59,7 @@ class EnvironmentMockGroup: self.mocks = {} def start(self): - """Create a patch object for class patches""" + """Create a patch object for class patches.""" for patch in self.patches: self.mocks[patch] = self.patches[patch].start() self.mocks['process'].communicate.return_value = ('', '') @@ -76,7 +77,7 @@ class EnvironmentMockGroup: pass def configure_mock(self, mock, kwargs): - """Configure object mocks""" + """Configure object mocks.""" self.mocks[mock].configure_mock(**kwargs) def __getattr__(self, name): diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py index eeff8aadb..9c6ff2515 100644 --- a/readthedocs/rtd_tests/mocks/mock_api.py +++ b/readthedocs/rtd_tests/mocks/mock_api.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Mock versions of many API-related classes.""" import json from contextlib import contextmanager diff --git a/readthedocs/rtd_tests/mocks/paths.py b/readthedocs/rtd_tests/mocks/paths.py index 3e4539ebc..787eabff9 100644 --- a/readthedocs/rtd_tests/mocks/paths.py +++ b/readthedocs/rtd_tests/mocks/paths.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Context managers to patch os.path.exists calls.""" import os import re diff --git a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py index c33f90ac9..e1a9267ad 100644 --- a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py +++ b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture import mock from django import urls @@ -59,7 +60,7 @@ class ProjectAdminActionsTest(TestCase): @mock.patch('readthedocs.projects.admin.broadcast') def test_project_delete(self, broadcast): - """Test project and artifacts are removed""" + """Test project and artifacts are removed.""" from readthedocs.projects.tasks import remove_dirs action_data = { ACTION_CHECKBOX_NAME: [self.project.pk], diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index 03d8f5f13..d7c4ab064 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -196,10 +196,7 @@ class APIBuildTests(TestCase): ) def test_response_building(self): - """ - The ``view docs`` attr should return a link - to the dashboard. - """ + """The ``view docs`` attr should return a link to the dashboard.""" client = APIClient() client.login(username='super', password='test') project = get( @@ -238,10 +235,7 @@ class APIBuildTests(TestCase): self.assertEqual(build['docs_url'], dashboard_url) def test_response_finished_and_success(self): - """ - The ``view docs`` attr should return a link - to the docs. - """ + """The ``view docs`` attr should return a link to the docs.""" client = APIClient() client.login(username='super', password='test') project = get( @@ -276,10 +270,7 @@ class APIBuildTests(TestCase): self.assertEqual(build['docs_url'], docs_url) def test_response_finished_and_fail(self): - """ - The ``view docs`` attr should return a link - to the dashboard. - """ + """The ``view docs`` attr should return a link to the dashboard.""" client = APIClient() client.login(username='super', password='test') project = get( @@ -1482,9 +1473,8 @@ class APIVersionTests(TestCase): ) def test_get_active_versions(self): - """ - Test the full response of ``/api/v2/version/?project__slug=pip&active=true`` - """ + """Test the full response of + ``/api/v2/version/?project__slug=pip&active=true``""" pip = Project.objects.get(slug='pip') data = QueryDict('', mutable=True) diff --git a/readthedocs/rtd_tests/tests/test_api_permissions.py b/readthedocs/rtd_tests/tests/test_api_permissions.py index d887be4f5..5a7d10a0a 100644 --- a/readthedocs/rtd_tests/tests/test_api_permissions.py +++ b/readthedocs/rtd_tests/tests/test_api_permissions.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from functools import partial from unittest import TestCase diff --git a/readthedocs/rtd_tests/tests/test_api_version_compare.py b/readthedocs/rtd_tests/tests/test_api_version_compare.py index 509cbcc76..24b94ad39 100644 --- a/readthedocs/rtd_tests/tests/test_api_version_compare.py +++ b/readthedocs/rtd_tests/tests/test_api_version_compare.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from readthedocs.builds.constants import LATEST diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index 500646797..8b31de913 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -30,8 +30,8 @@ class TestGitBackend(RTDTestCase): self.eric.set_password('test') self.eric.save() self.project = Project.objects.create( - name="Test Project", - repo_type="git", + name='Test Project', + repo_type='git', #Our top-level checkout repo=git_repo ) @@ -179,7 +179,7 @@ class TestGitBackend(RTDTestCase): def test_check_invalid_submodule_urls(self): repo = self.project.vcs_repo() repo.update() - r = repo.checkout('invalidsubmodule') + repo.checkout('invalidsubmodule') with self.assertRaises(RepositoryError) as e: repo.update_submodules(self.dummy_conf) # `invalid` is created in `make_test_git` diff --git a/readthedocs/rtd_tests/tests/test_backend_svn.py b/readthedocs/rtd_tests/tests/test_backend_svn.py index bd6f45511..eec702682 100644 --- a/readthedocs/rtd_tests/tests/test_backend_svn.py +++ b/readthedocs/rtd_tests/tests/test_backend_svn.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -"""Tests For SVN""" +"""Tests For SVN.""" from django_dynamic_fixture import get -from mock import patch from readthedocs.builds.models import Version from readthedocs.projects.models import Project diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py index b3ffbb7cd..f1129407b 100644 --- a/readthedocs/rtd_tests/tests/test_build_config.py +++ b/readthedocs/rtd_tests/tests/test_build_config.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from os import path import pytest @@ -16,10 +17,9 @@ V2_SCHEMA = path.join( class PathValidator(Validator): """ - Path validator + Path validator. - Checks if the given value is a string and a existing - file. + Checks if the given value is a string and a existing file. """ tag = 'path' diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 2b117fa4f..c56117804 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os import mock @@ -26,7 +27,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build(self, load_config): - '''Test full build''' + """Test full build.""" load_config.side_effect = create_load() project = get(Project, slug='project-1', @@ -36,7 +37,7 @@ class BuildEnvironmentTests(TestCase): version = project.versions.all()[0] self.mocks.configure_mock('api_versions', {'return_value': [version]}) self.mocks.configure_mock('api', { - 'get.return_value': {'downloads': "no_url_here"} + 'get.return_value': {'downloads': 'no_url_here'} }) self.mocks.patches['html_build'].stop() @@ -57,7 +58,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_respects_pdf_flag(self, load_config): - '''Build output format control''' + """Build output format control.""" load_config.side_effect = create_load() project = get(Project, slug='project-1', @@ -120,7 +121,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_respects_epub_flag(self, load_config): - '''Test build with epub enabled''' + """Test build with epub enabled.""" load_config.side_effect = create_load() project = get(Project, slug='project-1', @@ -148,7 +149,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_respects_yaml(self, load_config): - '''Test YAML build options''' + """Test YAML build options.""" load_config.side_effect = create_load({'formats': ['epub']}) project = get(Project, slug='project-1', @@ -177,7 +178,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_pdf_latex_failures(self, load_config): - '''Build failure if latex fails''' + """Build failure if latex fails.""" load_config.side_effect = create_load() self.mocks.patches['html_build'].stop() @@ -223,7 +224,7 @@ class BuildEnvironmentTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_pdf_latex_not_failure(self, load_config): - '''Test pass during PDF builds and bad latex failure status code''' + """Test pass during PDF builds and bad latex failure status code.""" load_config.side_effect = create_load() self.mocks.patches['html_build'].stop() diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index f7f16d0ae..48613710a 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -26,7 +26,11 @@ from readthedocs.rtd_tests.utils import ( class TestCeleryBuilding(RTDTestCase): - """These tests run the build functions directly. They don't use celery""" + """ + These tests run the build functions directly. + + They don't use celery + """ def setUp(self): repo = make_test_git() @@ -36,8 +40,8 @@ class TestCeleryBuilding(RTDTestCase): self.eric.set_password('test') self.eric.save() self.project = Project.objects.create( - name="Test Project", - repo_type="git", + name='Test Project', + repo_type='git', # Our top-level checkout repo=repo, ) diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 84c7fde76..2df44a08e 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -371,9 +371,7 @@ class TestLoadConfigV2: def test_build_formats_only_pdf( self, append_conf, html_build, build_docs_class, checkout_path, tmpdir): - """ - Only the pdf format is build. - """ + """Only the pdf format is build.""" checkout_path.return_value = str(tmpdir) self.create_config_file(tmpdir, {'formats': ['pdf']}) diff --git a/readthedocs/rtd_tests/tests/test_core_tags.py b/readthedocs/rtd_tests/tests/test_core_tags.py index 43a6b4f38..d119a5d5b 100644 --- a/readthedocs/rtd_tests/tests/test_core_tags.py +++ b/readthedocs/rtd_tests/tests/test_core_tags.py @@ -12,7 +12,7 @@ from readthedocs.projects.models import Project @override_settings(USE_SUBDOMAIN=False, PRODUCTION_DOMAIN='readthedocs.org') class CoreTagsTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): url_base = '{scheme}://{domain}/docs/pip{{version}}'.format( @@ -36,7 +36,7 @@ class CoreTagsTests(TestCase): with mock.patch('readthedocs.projects.models.broadcast'): self.client.login(username='eric', password='test') self.pip = Project.objects.get(slug='pip') - self.pip_fr = Project.objects.create(name="PIP-FR", slug='pip-fr', language='fr', main_language_project=self.pip) + self.pip_fr = Project.objects.create(name='PIP-FR', slug='pip-fr', language='fr', main_language_project=self.pip) def test_project_only(self): proj = Project.objects.get(slug='pip') diff --git a/readthedocs/rtd_tests/tests/test_core_utils.py b/readthedocs/rtd_tests/tests/test_core_utils.py index b44de84ce..a19b5db6a 100644 --- a/readthedocs/rtd_tests/tests/test_core_utils.py +++ b/readthedocs/rtd_tests/tests/test_core_utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Test core util functions""" +"""Test core util functions.""" import mock from django.test import TestCase @@ -30,7 +30,7 @@ class CoreUtilTests(TestCase): @mock.patch('readthedocs.projects.tasks.update_docs_task') def test_trigger_custom_queue(self, update_docs): - """Use a custom queue when routing the task""" + """Use a custom queue when routing the task.""" self.project.build_queue = 'build03' trigger_build(project=self.project, version=self.version) kwargs = { @@ -56,7 +56,7 @@ class CoreUtilTests(TestCase): @mock.patch('readthedocs.projects.tasks.update_docs_task') def test_trigger_build_time_limit(self, update_docs): - """Pass of time limit""" + """Pass of time limit.""" trigger_build(project=self.project, version=self.version) kwargs = { 'version_pk': self.version.pk, @@ -81,7 +81,7 @@ class CoreUtilTests(TestCase): @mock.patch('readthedocs.projects.tasks.update_docs_task') def test_trigger_build_invalid_time_limit(self, update_docs): - """Time limit as string""" + """Time limit as string.""" self.project.container_time_limit = '200s' trigger_build(project=self.project, version=self.version) kwargs = { @@ -107,7 +107,7 @@ class CoreUtilTests(TestCase): @mock.patch('readthedocs.projects.tasks.update_docs_task') def test_trigger_build_rounded_time_limit(self, update_docs): - """Time limit should round down""" + """Time limit should round down.""" self.project.container_time_limit = 3 trigger_build(project=self.project, version=self.version) kwargs = { @@ -132,7 +132,7 @@ class CoreUtilTests(TestCase): update_docs.signature().apply_async.assert_called() def test_slugify(self): - """Test additional slugify""" + """Test additional slugify.""" self.assertEqual(slugify('This is a test'), 'this-is-a-test') self.assertEqual(slugify('project_with_underscores-v.1.0'), diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index 7e06a2a3b..a8e42afdc 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -2,7 +2,6 @@ import django_dynamic_fixture as fixture import mock -import pytest from django.conf import settings from django.contrib.auth.models import User from django.http import Http404 diff --git a/readthedocs/rtd_tests/tests/test_domains.py b/readthedocs/rtd_tests/tests/test_domains.py index 377f0c8f6..cfa13b0c4 100644 --- a/readthedocs/rtd_tests/tests/test_domains.py +++ b/readthedocs/rtd_tests/tests/test_domains.py @@ -70,7 +70,7 @@ class FormTests(TestCase): self.project = get(Project, slug='kong') def test_https(self): - """Make sure https is an admin-only attribute""" + """Make sure https is an admin-only attribute.""" form = DomainForm({'domain': 'example.com', 'canonical': True}, project=self.project) self.assertTrue(form.is_valid()) @@ -82,7 +82,7 @@ class FormTests(TestCase): self.assertFalse(form.is_valid()) def test_canonical_change(self): - """Make sure canonical can be properly changed""" + """Make sure canonical can be properly changed.""" form = DomainForm({'domain': 'example.com', 'canonical': True}, project=self.project) self.assertTrue(form.is_valid()) diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py index e4bb82a98..1d452cf82 100644 --- a/readthedocs/rtd_tests/tests/test_extend.py +++ b/readthedocs/rtd_tests/tests/test_extend.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase, override_settings from readthedocs.core.utils.extend import ( @@ -34,7 +35,7 @@ class ExtendTests(TestCase): @override_settings(FOO_OVERRIDE_CLASS=None) def test_no_override(self): - """Test class without override""" + """Test class without override.""" class Foo(SettingsOverrideObject): _default_class = FooBase _override_setting = 'FOO_OVERRIDE_CLASS' @@ -49,7 +50,7 @@ class ExtendTests(TestCase): @override_settings(FOO_OVERRIDE_CLASS=EXTEND_OVERRIDE_PATH) def test_with_basic_override(self): - """Test class override setting defined""" + """Test class override setting defined.""" class Foo(SettingsOverrideObject): _default_class = FooBase _override_setting = 'FOO_OVERRIDE_CLASS' diff --git a/readthedocs/rtd_tests/tests/test_gold.py b/readthedocs/rtd_tests/tests/test_gold.py index db7c9f927..51d891eef 100644 --- a/readthedocs/rtd_tests/tests/test_gold.py +++ b/readthedocs/rtd_tests/tests/test_gold.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import fixture, get diff --git a/readthedocs/rtd_tests/tests/test_imported_file.py b/readthedocs/rtd_tests/tests/test_imported_file.py index fc1df6793..187b1af98 100644 --- a/readthedocs/rtd_tests/tests/test_imported_file.py +++ b/readthedocs/rtd_tests/tests/test_imported_file.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os from django.test import TestCase @@ -10,7 +11,7 @@ base_dir = os.path.dirname(os.path.dirname(__file__)) class ImportedFileTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): self.project = Project.objects.get(slug='pip') diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py index c35fffc78..e7be65883 100644 --- a/readthedocs/rtd_tests/tests/test_integrations.py +++ b/readthedocs/rtd_tests/tests/test_integrations.py @@ -1,8 +1,7 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture -from django.contrib.contenttypes.models import ContentType -from django.test import RequestFactory, TestCase -from rest_framework.response import Response -from rest_framework.test import APIClient, APIRequestFactory +from django.test import TestCase +from rest_framework.test import APIClient from readthedocs.integrations.models import ( GitHubWebhook, @@ -14,7 +13,8 @@ from readthedocs.projects.models import Project class HttpExchangeTests(TestCase): - """Test HttpExchange model by using existing views + """ + Test HttpExchange model by using existing views. This doesn't mock out a req/resp cycle, as manually creating these outside views misses a number of attributes on the request object. diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 0758e4d47..cd3df63ec 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -"""Notification tests""" +"""Notification tests.""" -from datetime import timedelta import django_dynamic_fixture as fixture import mock @@ -9,7 +8,6 @@ from django.contrib.auth.models import AnonymousUser, User from django.http import HttpRequest from django.test import TestCase from django.test.utils import override_settings -from django.utils import timezone from messages_extends.models import Message as PersistentMessage from readthedocs.builds.models import Build @@ -122,7 +120,7 @@ class NotificationBackendTests(TestCase): self.assertEqual(message.user, user) def test_message_anonymous_user(self, render_to_string): - """Anonymous user still throwns exception on persistent messages""" + """Anonymous user still throwns exception on persistent messages.""" render_to_string.return_value = 'Test' class TestNotification(Notification): diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index 1db3332cb..cc8c1009d 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -1,6 +1,7 @@ +# -*- coding: utf-8 -*- import json import logging -from urllib.parse import urlencode, urlparse +from urllib.parse import urlencode import mock from django.test import TestCase @@ -46,79 +47,79 @@ class BasePostCommitTest(TestCase): class GitLabWebHookTest(BasePostCommitTest): - fixtures = ["eric"] + fixtures = ['eric'] def setUp(self): self._setup() self.payload = { - "object_kind": "push", - "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", - "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "ref": "refs/heads/awesome", - "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "user_id": 4, - "user_name": "John Smith", - "user_email": "john@example.com", - "project_id": 15, - "project":{ - "name":"readthedocs", - "description":"", - "web_url":"http://example.com/mike/diaspora", - "avatar_url": None, - "git_ssh_url":"git@github.com:rtfd/readthedocs.org.git", - "git_http_url":"http://github.com/rtfd/readthedocs.org.git", - "namespace":"Mike", - "visibility_level":0, - "path_with_namespace":"mike/diaspora", - "default_branch":"master", - "homepage":"http://example.com/mike/diaspora", - "url":"git@github.com/rtfd/readthedocs.org.git", - "ssh_url":"git@github.com/rtfd/readthedocs.org.git", - "http_url":"http://github.com/rtfd/readthedocs.org.git" + 'object_kind': 'push', + 'before': '95790bf891e76fee5e1747ab589903a6a1f80f22', + 'after': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', + 'ref': 'refs/heads/awesome', + 'checkout_sha': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', + 'user_id': 4, + 'user_name': 'John Smith', + 'user_email': 'john@example.com', + 'project_id': 15, + 'project':{ + 'name':'readthedocs', + 'description':'', + 'web_url':'http://example.com/mike/diaspora', + 'avatar_url': None, + 'git_ssh_url':'git@github.com:rtfd/readthedocs.org.git', + 'git_http_url':'http://github.com/rtfd/readthedocs.org.git', + 'namespace':'Mike', + 'visibility_level':0, + 'path_with_namespace':'mike/diaspora', + 'default_branch':'master', + 'homepage':'http://example.com/mike/diaspora', + 'url':'git@github.com/rtfd/readthedocs.org.git', + 'ssh_url':'git@github.com/rtfd/readthedocs.org.git', + 'http_url':'http://github.com/rtfd/readthedocs.org.git' }, - "repository":{ - "name": "Diaspora", - "url": "git@github.com:rtfd/readthedocs.org.git", - "description": "", - "homepage": "http://github.com/rtfd/readthedocs.org", - "git_http_url": "http://github.com/rtfd/readthedocs.org.git", - "git_ssh_url": "git@github.com:rtfd/readthedocs.org.git", - "visibility_level": 0 + 'repository':{ + 'name': 'Diaspora', + 'url': 'git@github.com:rtfd/readthedocs.org.git', + 'description': '', + 'homepage': 'http://github.com/rtfd/readthedocs.org', + 'git_http_url': 'http://github.com/rtfd/readthedocs.org.git', + 'git_ssh_url': 'git@github.com:rtfd/readthedocs.org.git', + 'visibility_level': 0 }, - "commits": [ + 'commits': [ { - "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "message": "Update Catalan translation to e38cb41.", - "timestamp": "2011-12-12T14:27:31+02:00", - "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "author": { - "name": "Jordi Mallach", - "email": "jordi@softcatala.org" + 'id': 'b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327', + 'message': 'Update Catalan translation to e38cb41.', + 'timestamp': '2011-12-12T14:27:31+02:00', + 'url': 'http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327', + 'author': { + 'name': 'Jordi Mallach', + 'email': 'jordi@softcatala.org' }, - "added": ["CHANGELOG"], - "modified": ["app/controller/application.rb"], - "removed": [] + 'added': ['CHANGELOG'], + 'modified': ['app/controller/application.rb'], + 'removed': [] }, { - "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "message": "fixed readme", - "timestamp": "2012-01-03T23:36:29+02:00", - "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "author": { - "name": "GitLab dev user", - "email": "gitlabdev@dv6700.(none)" + 'id': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', + 'message': 'fixed readme', + 'timestamp': '2012-01-03T23:36:29+02:00', + 'url': 'http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7', + 'author': { + 'name': 'GitLab dev user', + 'email': 'gitlabdev@dv6700.(none)' }, - "added": ["CHANGELOG"], - "modified": ["app/controller/application.rb"], - "removed": [] + 'added': ['CHANGELOG'], + 'modified': ['app/controller/application.rb'], + 'removed': [] } ], - "total_commits_count": 4 + 'total_commits_count': 4 } def test_gitlab_post_commit_hook_builds_branch_docs_if_it_should(self): - """GitLab webhook should only build active versions""" + """GitLab webhook should only build active versions.""" r = self.client.post('/gitlab/', data=json.dumps(self.payload), content_type='application/json') self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') @@ -182,72 +183,72 @@ class GitLabWebHookTest(BasePostCommitTest): class GitHubWebHookTest(BasePostCommitTest): - fixtures = ["eric"] + fixtures = ['eric'] def setUp(self): self._setup() self.payload = { - "after": "5ad757394b926e5637ffeafe340f952ef48bd270", - "base_ref": "refs/heads/master", - "before": "5b4e453dc913b08642b1d4fb10ed23c9d6e5b129", - "commits": [ + 'after': '5ad757394b926e5637ffeafe340f952ef48bd270', + 'base_ref': 'refs/heads/master', + 'before': '5b4e453dc913b08642b1d4fb10ed23c9d6e5b129', + 'commits': [ { - "added": [], - "author": { - "email": "eric@ericholscher.com", - "name": "Eric Holscher", - "username": "ericholscher" + 'added': [], + 'author': { + 'email': 'eric@ericholscher.com', + 'name': 'Eric Holscher', + 'username': 'ericholscher' }, - "distinct": False, - "id": "11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a", - "message": "Fix it on the front list as well.", - "modified": [ - "readthedocs/templates/core/project_list_detailed.html" + 'distinct': False, + 'id': '11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a', + 'message': 'Fix it on the front list as well.', + 'modified': [ + 'readthedocs/templates/core/project_list_detailed.html' ], - "removed": [], - "timestamp": "2011-09-12T19:38:55-07:00", - "url": ("https://github.com/wraithan/readthedocs.org/" - "commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a") + 'removed': [], + 'timestamp': '2011-09-12T19:38:55-07:00', + 'url': ('https://github.com/wraithan/readthedocs.org/' + 'commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a') }, ], - "compare": ("https://github.com/wraithan/readthedocs.org/compare/" - "5b4e453...5ad7573"), - "created": False, - "deleted": False, - "forced": False, - "pusher": { - "name": "none" + 'compare': ('https://github.com/wraithan/readthedocs.org/compare/' + '5b4e453...5ad7573'), + 'created': False, + 'deleted': False, + 'forced': False, + 'pusher': { + 'name': 'none' }, - "ref": "refs/heads/awesome", - "repository": { - "created_at": "2011/09/09 14:20:13 -0700", - "description": "source code to readthedocs.org", - "fork": True, - "forks": 0, - "has_downloads": True, - "has_issues": False, - "has_wiki": True, - "homepage": "http://rtfd.org/", - "language": "Python", - "name": "readthedocs.org", - "open_issues": 0, - "owner": { - "email": "XWraithanX@gmail.com", - "name": "wraithan" + 'ref': 'refs/heads/awesome', + 'repository': { + 'created_at': '2011/09/09 14:20:13 -0700', + 'description': 'source code to readthedocs.org', + 'fork': True, + 'forks': 0, + 'has_downloads': True, + 'has_issues': False, + 'has_wiki': True, + 'homepage': 'http://rtfd.org/', + 'language': 'Python', + 'name': 'readthedocs.org', + 'open_issues': 0, + 'owner': { + 'email': 'XWraithanX@gmail.com', + 'name': 'wraithan' }, - "private": False, - "pushed_at": "2011/09/12 22:33:34 -0700", - "size": 140, - "url": "https://github.com/rtfd/readthedocs.org", - "ssh_url": "git@github.com:rtfd/readthedocs.org.git", - "watchers": 1 + 'private': False, + 'pushed_at': '2011/09/12 22:33:34 -0700', + 'size': 140, + 'url': 'https://github.com/rtfd/readthedocs.org', + 'ssh_url': 'git@github.com:rtfd/readthedocs.org.git', + 'watchers': 1 } } def test_post_types(self): - """Ensure various POST formats""" + """Ensure various POST formats.""" r = self.client.post('/github/', data=json.dumps(self.payload), content_type='application/json') @@ -261,6 +262,7 @@ class GitHubWebHookTest(BasePostCommitTest): """ Test the github post commit hook will build properly with upper case repository. + This allows for capitalization differences in post-commit hook URL's. """ payload = self.payload.copy() @@ -273,8 +275,8 @@ class GitHubWebHookTest(BasePostCommitTest): def test_400_on_no_ref(self): """ GitHub sometimes sends us a post-commit hook without a ref. - This means we don't know what branch to build, - so return a 400. + + This means we don't know what branch to build, so return a 400. """ payload = self.payload.copy() del payload['ref'] @@ -299,10 +301,9 @@ class GitHubWebHookTest(BasePostCommitTest): """ Test for private GitHub repo mapping. - Previously we were missing triggering post-commit hooks because - we only compared against the *public* ``github.com/user/repo`` URL. - Users can also enter a ``github.com:user/repo`` URL, - which we should support. + Previously we were missing triggering post-commit hooks because we only + compared against the *public* ``github.com/user/repo`` URL. Users can + also enter a ``github.com:user/repo`` URL, which we should support. """ self.rtfd.repo = 'git@github.com:rtfd/readthedocs.org' self.rtfd.save() @@ -368,7 +369,7 @@ class GitHubWebHookTest(BasePostCommitTest): class CorePostCommitTest(BasePostCommitTest): - fixtures = ["eric"] + fixtures = ['eric'] def setUp(self): self._setup() @@ -402,83 +403,83 @@ class BitBucketWebHookTest(BasePostCommitTest): self._setup() self.hg_payload = { - "canon_url": "https://bitbucket.org", - "commits": [ + 'canon_url': 'https://bitbucket.org', + 'commits': [ { - "author": "marcus", - "branch": "default", - "files": [ + 'author': 'marcus', + 'branch': 'default', + 'files': [ { - "file": "somefile.py", - "type": "modified" + 'file': 'somefile.py', + 'type': 'modified' } ], - "message": "Added some feature things", - "node": "d14d26a93fd2", - "parents": [ - "1b458191f31a" + 'message': 'Added some feature things', + 'node': 'd14d26a93fd2', + 'parents': [ + '1b458191f31a' ], - "raw_author": "Marcus Bertrand ", - "raw_node": "d14d26a93fd28d3166fa81c0cd3b6f339bb95bfe", - "revision": 3, - "size": -1, - "timestamp": "2012-05-30 06:07:03", - "utctimestamp": "2012-05-30 04:07:03+00:00" + 'raw_author': 'Marcus Bertrand ', + 'raw_node': 'd14d26a93fd28d3166fa81c0cd3b6f339bb95bfe', + 'revision': 3, + 'size': -1, + 'timestamp': '2012-05-30 06:07:03', + 'utctimestamp': '2012-05-30 04:07:03+00:00' } ], - "repository": { - "absolute_url": "/pip/pip/", - "fork": False, - "is_private": True, - "name": "Project X", - "owner": "marcus", - "scm": "hg", - "slug": "project-x", - "website": "" + 'repository': { + 'absolute_url': '/pip/pip/', + 'fork': False, + 'is_private': True, + 'name': 'Project X', + 'owner': 'marcus', + 'scm': 'hg', + 'slug': 'project-x', + 'website': '' }, - "user": "marcus" + 'user': 'marcus' } self.git_payload = { - "canon_url": "https://bitbucket.org", - "commits": [ + 'canon_url': 'https://bitbucket.org', + 'commits': [ { - "author": "marcus", - "branch": "master", - "files": [ + 'author': 'marcus', + 'branch': 'master', + 'files': [ { - "file": "somefile.py", - "type": "modified" + 'file': 'somefile.py', + 'type': 'modified' } ], - "message": "Added some more things to somefile.py\n", - "node": "620ade18607a", - "parents": [ - "702c70160afc" + 'message': 'Added some more things to somefile.py\n', + 'node': '620ade18607a', + 'parents': [ + '702c70160afc' ], - "raw_author": "Marcus Bertrand ", - "raw_node": "620ade18607ac42d872b568bb92acaa9a28620e9", - "revision": None, - "size": -1, - "timestamp": "2012-05-30 05:58:56", - "utctimestamp": "2012-05-30 03:58:56+00:00" + 'raw_author': 'Marcus Bertrand ', + 'raw_node': '620ade18607ac42d872b568bb92acaa9a28620e9', + 'revision': None, + 'size': -1, + 'timestamp': '2012-05-30 05:58:56', + 'utctimestamp': '2012-05-30 03:58:56+00:00' } ], - "repository": { - "absolute_url": "/sphinx/sphinx/", - "fork": False, - "is_private": True, - "name": "Project X", - "owner": "marcus", - "scm": "git", - "slug": "project-x", - "website": "https://atlassian.com/" + 'repository': { + 'absolute_url': '/sphinx/sphinx/', + 'fork': False, + 'is_private': True, + 'name': 'Project X', + 'owner': 'marcus', + 'scm': 'git', + 'slug': 'project-x', + 'website': 'https://atlassian.com/' }, - "user": "marcus" + 'user': 'marcus' } def test_post_types(self): - """Ensure various POST formats""" + """Ensure various POST formats.""" r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), content_type='application/json') @@ -554,7 +555,7 @@ class BitBucketWebHookTest(BasePostCommitTest): self.feature.projects.add(self.test_project) self.git_payload['commits'] = [{ - "branch": "integration", + 'branch': 'integration', }] self.git_payload['repository'] = { 'absolute_url': '/test/project/' diff --git a/readthedocs/rtd_tests/tests/test_privacy.py b/readthedocs/rtd_tests/tests/test_privacy.py index a5977ad86..e273eafc4 100644 --- a/readthedocs/rtd_tests/tests/test_privacy.py +++ b/readthedocs/rtd_tests/tests/test_privacy.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import json import logging @@ -33,7 +34,7 @@ class PrivacyTests(TestCase): version_privacy_level='private'): self.client.login(username='eric', password='test') log.info( - "Making kong with privacy: %s and version privacy: %s", + 'Making kong with privacy: %s and version privacy: %s', privacy_level, version_privacy_level, ) @@ -165,22 +166,22 @@ class PrivacyTests(TestCase): def test_public_repo_api(self): self._create_kong('public', 'public') self.client.login(username='eric', password='test') - resp = self.client.get("http://testserver/api/v1/project/django-kong/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) - resp = self.client.get("http://testserver/api/v1/project/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) self.client.login(username='tester', password='test') - resp = self.client.get("http://testserver/api/v1/project/django-kong/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) - resp = self.client.get("http://testserver/api/v1/project/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) @@ -188,21 +189,21 @@ class PrivacyTests(TestCase): def test_private_repo_api(self): self._create_kong('private', 'private') self.client.login(username='eric', password='test') - resp = self.client.get("http://testserver/api/v1/project/django-kong/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) - resp = self.client.get("http://testserver/api/v1/project/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) self.client.login(username='tester', password='test') - resp = self.client.get("http://testserver/api/v1/project/django-kong/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 404) - resp = self.client.get("http://testserver/api/v1/project/", - data={"format": "json"}) + resp = self.client.get('http://testserver/api/v1/project/', + data={'format': 'json'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 0) @@ -365,9 +366,7 @@ class PrivacyTests(TestCase): self.assertNotContains(r, 'test-slug') def test_queryset_chaining(self): - """ - Test that manager methods get set on related querysets. - """ + """Test that manager methods get set on related querysets.""" kong = self._create_kong('public', 'private') self.assertEqual( kong.versions.private().get(slug='latest').slug, diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index ed5fff8c2..4c874db49 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import re import mock @@ -90,10 +91,10 @@ class URLAccessMixin: for not_obj in self.context_data: if isinstance(obj, list) or isinstance(obj, set) or isinstance(obj, tuple): self.assertNotIn(not_obj, obj) - print("{} not in {}".format(not_obj, obj)) + print('{} not in {}'.format(not_obj, obj)) else: self.assertNotEqual(not_obj, obj) - print("{} is not {}".format(not_obj, obj)) + print('{} is not {}'.format(not_obj, obj)) def _test_url(self, urlpatterns): deconstructed_urls = extract_views_from_urlpatterns(urlpatterns) diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py index 69dde65c8..1131de23b 100644 --- a/readthedocs/rtd_tests/tests/test_profile_views.py +++ b/readthedocs/rtd_tests/tests/test_profile_views.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index 166426263..3e52496a5 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -169,7 +169,7 @@ class TestProjectForms(TestCase): data['tags'] = '{},{}'.format('a'*90, 'b'*100) form = ProjectExtraForm(data) self.assertTrue(form.is_valid()) - + data['tags'] = '{},{}'.format('a'*99, 'b'*101) form = ProjectExtraForm(data) self.assertFalse(form.is_valid()) diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index 473963b44..3e992e6bd 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -21,7 +21,8 @@ from readthedocs.projects.tasks import ( def get_filesystem(path, top_level_path=None): - """Recurse into path, return dictionary mapping of path and files + """ + Recurse into path, return dictionary mapping of path and files. This will return the path `path` as a nested dictionary of path objects. Directories are mapped to dictionary objects, file objects will have a @@ -255,7 +256,7 @@ class BaseSymlinkCnames: ) def test_symlink_cname_dont_link_missing_domains(self): - """Domains should be relinked after deletion""" + """Domains should be relinked after deletion.""" self.domain = get(Domain, project=self.project, domain='woot.com', url='http://woot.com', cname=True) self.symlink.symlink_cnames() @@ -320,7 +321,7 @@ class BaseSubprojects: self.symlink = self.symlink_class(self.project) def test_subproject_normal(self): - """Symlink pass adds symlink for subproject""" + """Symlink pass adds symlink for subproject.""" self.project.add_subproject(self.subproject) self.symlink.symlink_subprojects() filesystem = { @@ -362,7 +363,7 @@ class BaseSubprojects: self.assertFilesystem(filesystem) def test_subproject_alias(self): - """Symlink pass adds symlink for subproject alias""" + """Symlink pass adds symlink for subproject alias.""" self.project.add_subproject(self.subproject, alias='sweet-alias') self.symlink.symlink_subprojects() filesystem = { @@ -409,7 +410,7 @@ class BaseSubprojects: self.assertFilesystem(filesystem) def test_subproject_alias_with_spaces(self): - """Symlink pass adds symlink for subproject alias""" + """Symlink pass adds symlink for subproject alias.""" self.project.add_subproject(self.subproject, alias='Sweet Alias') self.symlink.symlink_subprojects() filesystem = { @@ -456,7 +457,7 @@ class BaseSubprojects: self.assertFilesystem(filesystem) def test_remove_subprojects(self): - """Nonexistent subprojects are unlinked""" + """Nonexistent subprojects are unlinked.""" self.project.add_subproject(self.subproject) self.symlink.symlink_subprojects() filesystem = { @@ -538,7 +539,7 @@ class BaseSymlinkTranslations: self.assertIn(self.translation, self.project.translations.all()) def test_symlink_basic(self): - """Test basic scenario, language english, translation german""" + """Test basic scenario, language english, translation german.""" self.symlink.symlink_translations() filesystem = { 'private_cname_project': {}, @@ -589,7 +590,7 @@ class BaseSymlinkTranslations: self.assertFilesystem(filesystem) def test_symlink_non_english(self): - """Test language german, translation english""" + """Test language german, translation english.""" self.project.language = 'de' self.translation.language = 'en' self.project.save() @@ -644,7 +645,8 @@ class BaseSymlinkTranslations: self.assertFilesystem(filesystem) def test_symlink_no_english(self): - """Test language german, no english + """ + Test language german, no english. This should symlink the translation to 'en' even though there is no 'en' language in translations or project language @@ -1057,8 +1059,8 @@ class TestPublicPrivateSymlink(TempSiteRootTestCase): """ Change subproject's ``privacy_level`` creates proper symlinks. - When the ``privacy_level`` changes in the subprojects, we need to - re-symlink the superproject also to keep in sync its symlink under the + When the ``privacy_level`` changes in the subprojects, we need to re- + symlink the superproject also to keep in sync its symlink under the private/public roots. """ filesystem_before = { diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py index 20dd5f5ec..b2d5c752c 100644 --- a/readthedocs/rtd_tests/tests/test_project_views.py +++ b/readthedocs/rtd_tests/tests/test_project_views.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from datetime import timedelta from django.contrib.auth.models import User @@ -51,7 +52,7 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): self.data['{}-current_step'.format(self.wizard_class_slug)] = 'extra' def test_profile_middleware_no_profile(self): - """User without profile and isn't banned""" + """User without profile and isn't banned.""" req = self.request('/projects/import', method='post', data=self.data) req.user = get(User, profile=None) resp = ImportWizardView.as_view()(req) @@ -60,7 +61,7 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): @patch('readthedocs.projects.views.private.ProjectBasicsForm.clean') def test_profile_middleware_spam(self, form): - """User will be banned""" + """User will be banned.""" form.side_effect = ProjectSpamError req = self.request('/projects/import', method='post', data=self.data) req.user = get(User) @@ -70,7 +71,7 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase): self.assertTrue(req.user.profile.banned) def test_profile_middleware_banned(self): - """User is banned""" + """User is banned.""" req = self.request('/projects/import', method='post', data=self.data) req.user = get(User) req.user.profile.banned = True @@ -103,7 +104,7 @@ class TestBasicsForm(WizardTestCase): return super().request(*args, **kwargs) def test_form_pass(self): - """Only submit the basics""" + """Only submit the basics.""" resp = self.post_step('basics') self.assertIsInstance(resp, HttpResponseRedirect) self.assertEqual(resp.status_code, 302) @@ -135,7 +136,7 @@ class TestBasicsForm(WizardTestCase): self.assertWizardFailure(resp, 'remote_repository') def test_form_missing(self): - """Submit form with missing data, expect to get failures""" + """Submit form with missing data, expect to get failures.""" self.step_data['basics'] = {'advanced': True} resp = self.post_step('basics') self.assertWizardFailure(resp, 'name') @@ -155,7 +156,7 @@ class TestAdvancedForm(TestBasicsForm): } def test_form_pass(self): - """Test all forms pass validation""" + """Test all forms pass validation.""" resp = self.post_step('basics') self.assertWizardResponse(resp, 'extra') resp = self.post_step('extra', session=list(resp._request.session.items())) @@ -176,7 +177,7 @@ class TestAdvancedForm(TestBasicsForm): self.assertEqual(getattr(proj, key), val) def test_form_missing_extra(self): - """Submit extra form with missing data, expect to get failures""" + """Submit extra form with missing data, expect to get failures.""" # Remove extra data to trigger validation errors self.step_data['extra'] = {} @@ -204,7 +205,7 @@ class TestAdvancedForm(TestBasicsForm): @patch('readthedocs.projects.views.private.ProjectExtraForm.clean_description', create=True) def test_form_spam(self, mocked_validator): - """Don't add project on a spammy description""" + """Don't add project on a spammy description.""" self.user.date_joined = timezone.now() - timedelta(days=365) self.user.save() mocked_validator.side_effect = ProjectSpamError @@ -226,7 +227,7 @@ class TestAdvancedForm(TestBasicsForm): @patch('readthedocs.projects.views.private.ProjectExtraForm.clean_description', create=True) def test_form_spam_ban_user(self, mocked_validator): - """Don't add spam and ban new user""" + """Don't add spam and ban new user.""" self.user.date_joined = timezone.now() self.user.save() mocked_validator.side_effect = ProjectSpamError @@ -247,7 +248,7 @@ class TestAdvancedForm(TestBasicsForm): class TestImportDemoView(MockBuildTestCase): - """Test project import demo view""" + """Test project import demo view.""" fixtures = ['test_data', 'eric'] @@ -264,7 +265,7 @@ class TestImportDemoView(MockBuildTestCase): self.assertEqual(messages[0].level, message_const.SUCCESS) def test_import_demo_already_imported(self): - """Import demo project multiple times, expect failure 2nd post""" + """Import demo project multiple times, expect failure 2nd post.""" self.test_import_demo_pass() project = Project.objects.get(slug='eric-demo') @@ -281,7 +282,7 @@ class TestImportDemoView(MockBuildTestCase): Project.objects.get(slug='eric-demo')) def test_import_demo_another_user_imported(self): - """Import demo project after another user, expect success""" + """Import demo project after another user, expect success.""" self.test_import_demo_pass() project = Project.objects.get(slug='eric-demo') @@ -297,7 +298,7 @@ class TestImportDemoView(MockBuildTestCase): self.assertEqual(messages[0].level, message_const.SUCCESS) def test_import_demo_imported_renamed(self): - """If the demo project is renamed, don't import another""" + """If the demo project is renamed, don't import another.""" self.test_import_demo_pass() project = Project.objects.get(slug='eric-demo') project.name = 'eric-demo-foobar' @@ -318,7 +319,8 @@ class TestImportDemoView(MockBuildTestCase): Project.objects.get(slug='eric-demo')) def test_import_demo_imported_duplicate(self): - """If a project exists with same name, expect a failure importing demo + """ + If a project exists with same name, expect a failure importing demo. This should be edge case, user would have to import a project (not the demo project), named user-demo, and then manually enter the demo import @@ -403,7 +405,7 @@ class TestPrivateMixins(MockBuildTestCase): self.domain = get(Domain, project=self.project) def test_project_relation(self): - """Class using project relation mixin class""" + """Class using project relation mixin class.""" class FoobarView(ProjectRelationMixin, ContextMixin): model = Domain diff --git a/readthedocs/rtd_tests/tests/test_redirects.py b/readthedocs/rtd_tests/tests/test_redirects.py index fad6cd2f4..834b46568 100644 --- a/readthedocs/rtd_tests/tests/test_redirects.py +++ b/readthedocs/rtd_tests/tests/test_redirects.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import logging from django.http import Http404 @@ -14,7 +15,7 @@ from readthedocs.redirects.models import Redirect @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False, APPEND_SLASH=False) class RedirectTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): logging.disable(logging.DEBUG) @@ -105,7 +106,7 @@ class RedirectTests(TestCase): @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False) class RedirectAppTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): self.client.login(username='eric', password='test') @@ -336,7 +337,7 @@ class CustomRedirectTests(TestCase): @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False) class RedirectBuildTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): self.project = get(Project, @@ -359,10 +360,10 @@ class RedirectBuildTests(TestCase): @override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False) class GetFullPathTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): - self.proj = Project.objects.get(slug="read-the-docs") + self.proj = Project.objects.get(slug='read-the-docs') self.redirect = get(Redirect, project=self.proj) def test_http_filenames_return_themselves(self): diff --git a/readthedocs/rtd_tests/tests/test_search_json_parsing.py b/readthedocs/rtd_tests/tests/test_search_json_parsing.py index 3836f72d1..d2ac34e34 100644 --- a/readthedocs/rtd_tests/tests/test_search_json_parsing.py +++ b/readthedocs/rtd_tests/tests/test_search_json_parsing.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os from django.test import TestCase diff --git a/readthedocs/rtd_tests/tests/test_single_version.py b/readthedocs/rtd_tests/tests/test_single_version.py index bb850d932..567e96b5f 100644 --- a/readthedocs/rtd_tests/tests/test_single_version.py +++ b/readthedocs/rtd_tests/tests/test_single_version.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture from django.test import TestCase from django.test.utils import override_settings diff --git a/readthedocs/rtd_tests/tests/test_subprojects.py b/readthedocs/rtd_tests/tests/test_subprojects.py index 030fb1ecc..d6545189e 100644 --- a/readthedocs/rtd_tests/tests/test_subprojects.py +++ b/readthedocs/rtd_tests/tests/test_subprojects.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import django_dynamic_fixture as fixture import mock from django.contrib.auth.models import User diff --git a/readthedocs/rtd_tests/tests/test_sync_versions.py b/readthedocs/rtd_tests/tests/test_sync_versions.py index 6c41de5b8..d5686dcec 100644 --- a/readthedocs/rtd_tests/tests/test_sync_versions.py +++ b/readthedocs/rtd_tests/tests/test_sync_versions.py @@ -2,7 +2,6 @@ import json -import pytest from django.test import TestCase from django.urls import reverse @@ -447,13 +446,11 @@ class TestSyncVersions(TestCase): self.assertTrue(current_stable.machine) def test_machine_attr_when_user_define_stable_branch_and_delete_it_new_project(self): - """ - The user imports a new project with a branch named ``stable``, - when syncing the versions, the RTD's ``stable`` is lost - (set to machine=False) and doesn't update automatically anymore, - when the branch is deleted on the user repository, the RTD's ``stable`` - is back (set to machine=True). - """ + """The user imports a new project with a branch named ``stable``, when + syncing the versions, the RTD's ``stable`` is lost (set to + machine=False) and doesn't update automatically anymore, when the branch + is deleted on the user repository, the RTD's ``stable`` is back (set to + machine=True).""" # There isn't a stable version yet self.pip.versions.exclude(slug='master').delete() current_stable = self.pip.get_stable_version() @@ -525,13 +522,11 @@ class TestSyncVersions(TestCase): self.assertTrue(current_stable.machine) def test_machine_attr_when_user_define_latest_tag_and_delete_it(self): - """ - The user creates a tag named ``latest`` on an existing repo, - when syncing the versions, the RTD's ``latest`` is lost - (set to machine=False) and doesn't update automatically anymore, - when the tag is deleted on the user repository, the RTD's ``latest`` - is back (set to machine=True). - """ + """The user creates a tag named ``latest`` on an existing repo, when + syncing the versions, the RTD's ``latest`` is lost (set to + machine=False) and doesn't update automatically anymore, when the tag is + deleted on the user repository, the RTD's ``latest`` is back (set to + machine=True).""" version_post_data = { 'branches': [ { @@ -589,13 +584,11 @@ class TestSyncVersions(TestCase): self.assertTrue(version_latest.machine) def test_machine_attr_when_user_define_latest_branch_and_delete_it(self): - """ - The user creates a branch named ``latest`` on an existing repo, - when syncing the versions, the RTD's ``latest`` is lost - (set to machine=False) and doesn't update automatically anymore, - when the branch is deleted on the user repository, the RTD's ``latest`` - is back (set to machine=True). - """ + """The user creates a branch named ``latest`` on an existing repo, when + syncing the versions, the RTD's ``latest`` is lost (set to + machine=False) and doesn't update automatically anymore, when the branch + is deleted on the user repository, the RTD's ``latest`` is back (set to + machine=True).""" version_post_data = { 'branches': [ { diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py index 4a10e1495..cfa81237e 100644 --- a/readthedocs/rtd_tests/tests/test_urls.py +++ b/readthedocs/rtd_tests/tests/test_urls.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from django.urls import NoReverseMatch, reverse diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py index b690ef3db..2dfd47258 100644 --- a/readthedocs/rtd_tests/tests/test_version_commit_name.py +++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from django_dynamic_fixture import get, new diff --git a/readthedocs/rtd_tests/tests/test_version_config.py b/readthedocs/rtd_tests/tests/test_version_config.py index 2c79686aa..f97c9c871 100644 --- a/readthedocs/rtd_tests/tests/test_version_config.py +++ b/readthedocs/rtd_tests/tests/test_version_config.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from django.test import TestCase from django_dynamic_fixture import get diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py index a193156e1..63d3ae385 100644 --- a/readthedocs/rtd_tests/tests/test_version_slug.py +++ b/readthedocs/rtd_tests/tests/test_version_slug.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import re from django.test import TestCase @@ -26,7 +27,7 @@ class VersionSlugPatternTests(TestCase): class VersionSlugFieldTests(TestCase): - fixtures = ["eric", "test_data"] + fixtures = ['eric', 'test_data'] def setUp(self): self.pip = Project.objects.get(slug='pip') diff --git a/readthedocs/rtd_tests/utils.py b/readthedocs/rtd_tests/utils.py index 323dc07ad..95c0d9938 100644 --- a/readthedocs/rtd_tests/utils.py +++ b/readthedocs/rtd_tests/utils.py @@ -20,9 +20,7 @@ log = logging.getLogger(__name__) def get_readthedocs_app_path(): - """ - Return the absolute path of the ``readthedocs`` app. - """ + """Return the absolute path of the ``readthedocs`` app.""" try: import readthedocs diff --git a/readthedocs/search/tests/conftest.py b/readthedocs/search/tests/conftest.py index 1577b6b04..7b21c7c21 100644 --- a/readthedocs/search/tests/conftest.py +++ b/readthedocs/search/tests/conftest.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import random import string from random import shuffle diff --git a/readthedocs/search/tests/data/docs/story.json b/readthedocs/search/tests/data/docs/story.json index 69226b652..10c81a978 100644 --- a/readthedocs/search/tests/data/docs/story.json +++ b/readthedocs/search/tests/data/docs/story.json @@ -29,4 +29,4 @@ } ], "path": "open-source-philosophy" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/data/docs/wiping.json b/readthedocs/search/tests/data/docs/wiping.json index a54889e05..bbdbc8860 100644 --- a/readthedocs/search/tests/data/docs/wiping.json +++ b/readthedocs/search/tests/data/docs/wiping.json @@ -12,4 +12,4 @@ } ], "path": "guides/wipe-environment" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/data/kuma/docker.json b/readthedocs/search/tests/data/kuma/docker.json index 3f8676407..6e16f7e97 100644 --- a/readthedocs/search/tests/data/kuma/docker.json +++ b/readthedocs/search/tests/data/kuma/docker.json @@ -22,4 +22,4 @@ } ], "path": "docker" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/data/kuma/documentation.json b/readthedocs/search/tests/data/kuma/documentation.json index 310a01d05..8c7b44a42 100644 --- a/readthedocs/search/tests/data/kuma/documentation.json +++ b/readthedocs/search/tests/data/kuma/documentation.json @@ -18,4 +18,4 @@ } ], "path": "documentation" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/data/pipeline/installation.json b/readthedocs/search/tests/data/pipeline/installation.json index 30fb78d1d..22bba4f08 100644 --- a/readthedocs/search/tests/data/pipeline/installation.json +++ b/readthedocs/search/tests/data/pipeline/installation.json @@ -30,4 +30,4 @@ } ], "path": "installation" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/data/pipeline/signals.json b/readthedocs/search/tests/data/pipeline/signals.json index 3bf3a8053..abed6187b 100644 --- a/readthedocs/search/tests/data/pipeline/signals.json +++ b/readthedocs/search/tests/data/pipeline/signals.json @@ -24,4 +24,4 @@ } ], "path": "signals" -} \ No newline at end of file +} diff --git a/readthedocs/search/tests/dummy_data.py b/readthedocs/search/tests/dummy_data.py index f46449374..2e99d4e77 100644 --- a/readthedocs/search/tests/dummy_data.py +++ b/readthedocs/search/tests/dummy_data.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import json import os @@ -15,7 +16,7 @@ def _get_dummy_json(): data = [] for file_name in value: current_path = os.path.abspath(os.path.dirname(__file__)) - path = os.path.join(current_path, "data", key, file_name) + path = os.path.join(current_path, 'data', key, file_name) with open(path) as f: content = json.load(f) data.append(content) diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py index d5ac18baf..fa740285d 100644 --- a/readthedocs/search/tests/test_views.py +++ b/readthedocs/search/tests/test_views.py @@ -41,7 +41,7 @@ class TestElasticSearch: assert project.name.encode('utf-8') in result.text().encode('utf-8') def test_search_project_show_languages(self, client, project, es_index): - """Test that searching project should show all available languages""" + """Test that searching project should show all available languages.""" # Create a project in bn and add it as a translation G(Project, language='bn', name=project.name) self._reindex_elasticsearch(es_index=es_index) @@ -55,7 +55,7 @@ class TestElasticSearch: assert 'bn' in content.text() def test_search_project_filter_language(self, client, project, es_index): - """Test that searching project filtered according to language""" + """Test that searching project filtered according to language.""" # Create a project in bn and add it as a translation translate = G(Project, language='bn', name=project.name) self._reindex_elasticsearch(es_index=es_index) @@ -83,7 +83,8 @@ class TestElasticSearch: assert len(result) == 1 def test_file_search_show_projects(self, client): - """Test that search result page shows list of projects while searching for files""" + """Test that search result page shows list of projects while searching + for files.""" # `Github` word is present both in `kuma` and `pipeline` files # so search with this phrase @@ -102,7 +103,7 @@ class TestElasticSearch: assert 'kuma' and 'pipeline' in text def test_file_search_filter_by_project(self, client): - """Test that search result are filtered according to project""" + """Test that search result are filtered according to project.""" # `Github` word is present both in `kuma` and `pipeline` files # so search with this phrase but filter through `kuma` project @@ -122,11 +123,11 @@ class TestElasticSearch: # as the query is present in both projects content = page.find('.navigable .project-list') if len(content) != 2: - pytest.xfail("failing because currently all projects are not showing in project list") + pytest.xfail('failing because currently all projects are not showing in project list') else: assert 'kuma' and 'pipeline' in content.text() - @pytest.mark.xfail(reason="Versions are not showing correctly! Fixme while rewrite!") + @pytest.mark.xfail(reason='Versions are not showing correctly! Fixme while rewrite!') def test_file_search_show_versions(self, client, all_projects, es_index, settings): # override the settings to index all versions settings.INDEX_ONLY_LATEST = False @@ -161,7 +162,7 @@ class TestElasticSearch: assert sorted(project_versions) == sorted(content_versions) def test_file_search_subprojects(self, client, all_projects, es_index): - """File search should return results from subprojects also""" + """File search should return results from subprojects also.""" project = all_projects[0] subproject = all_projects[1] # Add another project as subproject of the project diff --git a/readthedocs/search/tests/utils.py b/readthedocs/search/tests/utils.py index a48ea83dd..80bf0fadf 100644 --- a/readthedocs/search/tests/utils.py +++ b/readthedocs/search/tests/utils.py @@ -1,9 +1,12 @@ +# -*- coding: utf-8 -*- from readthedocs.search.tests.dummy_data import DUMMY_PAGE_JSON def get_search_query_from_project_file(project_slug, page_num=0, data_type='title'): - """Return search query from the project's page file. - Query is generated from the value of `data_type` + """ + Return search query from the project's page file. + + Query is generated from the value of `data_type` """ all_pages = DUMMY_PAGE_JSON[project_slug] diff --git a/readthedocs/vcs_support/tests.py b/readthedocs/vcs_support/tests.py index 2b16090ba..d43f8c0f4 100644 --- a/readthedocs/vcs_support/tests.py +++ b/readthedocs/vcs_support/tests.py @@ -1,9 +1,9 @@ +# -*- coding: utf-8 -*- import os import shutil import unittest import mock -from django.conf import settings from readthedocs.vcs_support import utils From 7d08eed4283ec33628b96c9041c41b45de5b10a0 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:49:11 +0100 Subject: [PATCH 58/65] Run add-trailing-comma over all the files --- readthedocs/api/utils.py | 2 +- readthedocs/builds/admin.py | 6 +- readthedocs/builds/models.py | 54 ++-- readthedocs/builds/querysets.py | 2 +- readthedocs/builds/views.py | 2 +- readthedocs/config/config.py | 22 +- readthedocs/config/tests/test_config.py | 204 ++++++++------ readthedocs/config/tests/test_parser.py | 3 +- readthedocs/core/context_processors.py | 4 +- .../core/management/commands/archive.py | 5 +- .../core/management/commands/clean_builds.py | 2 +- .../core/management/commands/import_github.py | 7 +- .../commands/import_github_language.py | 14 +- .../core/management/commands/set_metadata.py | 2 +- .../core/management/commands/symlink.py | 5 +- readthedocs/core/middleware.py | 43 +-- readthedocs/core/models.py | 2 +- readthedocs/core/resolver.py | 2 +- readthedocs/core/symlink.py | 17 +- readthedocs/core/tasks.py | 2 +- readthedocs/core/templatetags/core_tags.py | 2 +- readthedocs/core/templatetags/privacy_tags.py | 2 +- readthedocs/core/urls/single_version.py | 2 +- readthedocs/core/urls/subdomain.py | 22 +- readthedocs/core/views/hooks.py | 14 +- readthedocs/core/views/serve.py | 8 +- readthedocs/doc_builder/backends/mkdocs.py | 6 +- readthedocs/doc_builder/constants.py | 2 +- readthedocs/doc_builder/environments.py | 18 +- readthedocs/doc_builder/exceptions.py | 2 +- readthedocs/gold/forms.py | 4 +- readthedocs/gold/tests/test_forms.py | 147 +++++----- readthedocs/gold/urls.py | 4 +- readthedocs/integrations/admin.py | 6 +- readthedocs/notifications/forms.py | 6 +- .../0004_drop_github_and_bitbucket_models.py | 6 +- .../migrations/0006_move_oauth_source.py | 12 +- readthedocs/oauth/models.py | 20 +- readthedocs/oauth/services/bitbucket.py | 2 +- readthedocs/payments/forms.py | 10 +- readthedocs/projects/admin.py | 2 +- readthedocs/projects/forms.py | 72 ++--- .../migrations/0007_migrate_canonical_data.py | 4 +- .../migrations/0010_migrate_domain_data.py | 3 +- .../0021_add-webhook-deprecation-feature.py | 2 +- .../migrations/0023_migrate-alias-slug.py | 2 +- readthedocs/projects/models.py | 227 ++++++++------- readthedocs/projects/tasks.py | 41 +-- readthedocs/projects/urls/private.py | 176 ++++++------ readthedocs/projects/version_handling.py | 8 +- readthedocs/projects/views/private.py | 2 +- readthedocs/projects/views/public.py | 19 +- readthedocs/restapi/views/core_views.py | 8 +- readthedocs/restapi/views/footer_views.py | 6 +- readthedocs/restapi/views/integrations.py | 5 +- readthedocs/restapi/views/model_views.py | 14 +- readthedocs/restapi/views/search_views.py | 6 +- readthedocs/rtd_tests/base.py | 2 +- readthedocs/rtd_tests/files/conf.py | 8 +- .../fixtures/sample_repo/source/conf.py | 12 +- readthedocs/rtd_tests/mocks/environment.py | 54 ++-- .../tests/projects/test_admin_actions.py | 8 +- readthedocs/rtd_tests/tests/test_api.py | 102 ++++--- .../rtd_tests/tests/test_api_permissions.py | 12 +- readthedocs/rtd_tests/tests/test_backend.py | 20 +- .../rtd_tests/tests/test_build_config.py | 48 ++-- .../rtd_tests/tests/test_build_forms.py | 4 +- .../tests/test_build_notifications.py | 12 +- readthedocs/rtd_tests/tests/test_builds.py | 142 +++++----- readthedocs/rtd_tests/tests/test_celery.py | 54 ++-- .../tests/test_config_integration.py | 239 +++++++++------- .../rtd_tests/tests/test_core_utils.py | 30 +- .../rtd_tests/tests/test_doc_builder.py | 44 +-- .../rtd_tests/tests/test_doc_building.py | 143 ++++++---- .../rtd_tests/tests/test_doc_serving.py | 12 +- readthedocs/rtd_tests/tests/test_domains.py | 36 ++- readthedocs/rtd_tests/tests/test_extend.py | 20 +- readthedocs/rtd_tests/tests/test_gold.py | 2 +- .../rtd_tests/tests/test_integrations.py | 84 +++--- .../rtd_tests/tests/test_middleware.py | 4 +- .../rtd_tests/tests/test_notifications.py | 38 ++- readthedocs/rtd_tests/tests/test_oauth.py | 62 +++-- .../rtd_tests/tests/test_post_commit_hooks.py | 258 +++++++++++------- readthedocs/rtd_tests/tests/test_privacy.py | 117 +++++--- .../rtd_tests/tests/test_privacy_urls.py | 27 +- .../rtd_tests/tests/test_profile_views.py | 12 +- readthedocs/rtd_tests/tests/test_project.py | 77 +++--- .../rtd_tests/tests/test_project_forms.py | 44 +-- .../rtd_tests/tests/test_project_querysets.py | 4 +- .../rtd_tests/tests/test_project_symlinks.py | 244 ++++++++++------- .../rtd_tests/tests/test_project_views.py | 51 ++-- readthedocs/rtd_tests/tests/test_redirects.py | 177 +++++++----- readthedocs/rtd_tests/tests/test_resolver.py | 107 +++++--- .../rtd_tests/tests/test_restapi_client.py | 2 +- .../tests/test_search_json_parsing.py | 2 +- .../rtd_tests/tests/test_single_version.py | 26 +- .../rtd_tests/tests/test_subprojects.py | 52 ++-- .../rtd_tests/tests/test_sync_versions.py | 76 +++--- readthedocs/rtd_tests/tests/test_urls.py | 14 +- .../tests/test_version_commit_name.py | 54 ++-- .../rtd_tests/tests/test_version_config.py | 8 +- .../rtd_tests/tests/test_version_slug.py | 30 +- readthedocs/rtd_tests/tests/test_views.py | 14 +- readthedocs/rtd_tests/utils.py | 16 +- readthedocs/search/indexes.py | 10 +- readthedocs/search/lib.py | 16 +- readthedocs/search/parse_json.py | 6 +- readthedocs/search/tests/test_views.py | 54 ++-- readthedocs/search/utils.py | 8 +- readthedocs/urls.py | 4 +- readthedocs/vcs_support/backends/bzr.py | 2 +- readthedocs/vcs_support/backends/git.py | 8 +- readthedocs/vcs_support/backends/hg.py | 4 +- readthedocs/vcs_support/tests.py | 36 ++- readthedocs/vcs_support/utils.py | 4 +- 115 files changed, 2396 insertions(+), 1652 deletions(-) diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py index ab820d21d..c5ecd60cd 100644 --- a/readthedocs/api/utils.py +++ b/readthedocs/api/utils.py @@ -48,7 +48,7 @@ class EnhancedModelResource(ModelResource): ugettext( 'Invalid resource lookup data provided ' '(mismatched type).: %(error)s', - ) % {'error': e} + ) % {'error': e}, ) diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py index b4bc390b9..fbd83d8e3 100644 --- a/readthedocs/builds/admin.py +++ b/readthedocs/builds/admin.py @@ -16,10 +16,10 @@ class BuildCommandResultInline(admin.TabularInline): class BuildAdmin(admin.ModelAdmin): fields = ( 'project', 'version', 'type', 'state', 'error', 'success', 'length', - 'cold_storage' + 'cold_storage', ) list_display = ( - 'id', 'project', 'version_name', 'success', 'type', 'state', 'date' + 'id', 'project', 'version_name', 'success', 'type', 'state', 'date', ) list_filter = ('type', 'state', 'success') list_select_related = ('project', 'version') @@ -34,7 +34,7 @@ class BuildAdmin(admin.ModelAdmin): class VersionAdmin(GuardedModelAdmin): search_fields = ('slug', 'project__name') list_display = ( - 'slug', 'type', 'project', 'privacy_level', 'active', 'built' + 'slug', 'type', 'project', 'privacy_level', 'active', 'built', ) list_filter = ('type', 'privacy_level', 'active', 'built') raw_id_fields = ('project',) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index e4fcad037..2f8eeae8b 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -50,7 +50,7 @@ from .version_slug import VersionSlugField DEFAULT_VERSION_PRIVACY_LEVEL = getattr( - settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public' + settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public', ) log = logging.getLogger(__name__) @@ -91,7 +91,7 @@ class Version(models.Model): #: filesystem to determine how the paths for this version are called. It #: must not be used for any other identifying purposes. slug = VersionSlugField( - _('Slug'), max_length=255, populate_from='verbose_name' + _('Slug'), max_length=255, populate_from='verbose_name', ) supported = models.BooleanField(_('Supported'), default=True) @@ -125,7 +125,7 @@ class Version(models.Model): version=self.verbose_name, project=self.project, pk=self.pk, - ) + ), ) @property @@ -137,8 +137,10 @@ class Version(models.Model): :rtype: dict """ last_build = ( - self.builds.filter(state='finished', - success=True).order_by('-date').first() + self.builds.filter( + state='finished', + success=True, + ).order_by('-date').first() ) return last_build.config @@ -182,7 +184,7 @@ class Version(models.Model): # If we came that far it's not a special version nor a branch or tag. # Therefore just return the identifier to make a safe guess. log.debug( - 'TODO: Raise an exception here. Testing what cases it happens' + 'TODO: Raise an exception here. Testing what cases it happens', ) return self.identifier @@ -197,7 +199,7 @@ class Version(models.Model): ) private = self.privacy_level == PRIVATE return self.project.get_docs_url( - version_slug=self.slug, private=private + version_slug=self.slug, private=private, ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -207,7 +209,7 @@ class Version(models.Model): for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( - type='app', task=tasks.symlink_project, args=[self.project.pk] + type='app', task=tasks.symlink_project, args=[self.project.pk], ) return obj @@ -254,22 +256,22 @@ class Version(models.Model): data['PDF'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['HTML'] = project.get_production_media_url( - 'htmlzip', self.slug + 'htmlzip', self.slug, ) if project.has_epub(self.slug): data['Epub'] = project.get_production_media_url( - 'epub', self.slug + 'epub', self.slug, ) else: if project.has_pdf(self.slug): data['pdf'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['htmlzip'] = project.get_production_media_url( - 'htmlzip', self.slug + 'htmlzip', self.slug, ) if project.has_epub(self.slug): data['epub'] = project.get_production_media_url( - 'epub', self.slug + 'epub', self.slug, ) return data @@ -319,7 +321,7 @@ class Version(models.Model): log.exception('Build path cleanup failed') def get_github_url( - self, docroot, filename, source_suffix='.rst', action='view' + self, docroot, filename, source_suffix='.rst', action='view', ): """ Return a GitHub URL for a given filename. @@ -362,7 +364,7 @@ class Version(models.Model): ) def get_gitlab_url( - self, docroot, filename, source_suffix='.rst', action='view' + self, docroot, filename, source_suffix='.rst', action='view', ): repo_url = self.project.repo if 'gitlab' not in repo_url: @@ -460,16 +462,16 @@ class Build(models.Model): """Build data.""" project = models.ForeignKey( - Project, verbose_name=_('Project'), related_name='builds' + Project, verbose_name=_('Project'), related_name='builds', ) version = models.ForeignKey( - Version, verbose_name=_('Version'), null=True, related_name='builds' + Version, verbose_name=_('Version'), null=True, related_name='builds', ) type = models.CharField( - _('Type'), max_length=55, choices=BUILD_TYPES, default='html' + _('Type'), max_length=55, choices=BUILD_TYPES, default='html', ) state = models.CharField( - _('State'), max_length=55, choices=BUILD_STATE, default='finished' + _('State'), max_length=55, choices=BUILD_STATE, default='finished', ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) @@ -480,18 +482,18 @@ class Build(models.Model): error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( - _('Commit'), max_length=255, null=True, blank=True + _('Commit'), max_length=255, null=True, blank=True, ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( - _('Builder'), max_length=255, null=True, blank=True + _('Builder'), max_length=255, null=True, blank=True, ) cold_storage = models.NullBooleanField( - _('Cold Storage'), help_text='Build steps stored outside the database.' + _('Cold Storage'), help_text='Build steps stored outside the database.', ) # Manager @@ -563,8 +565,10 @@ class Build(models.Model): """ if self.pk is None or self._config_changed: previous = self.previous - if (previous is not None and self._config and - self._config == previous.config): + if ( + previous is not None and self._config and + self._config == previous.config + ): previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} super().save(*args, **kwargs) @@ -578,7 +582,7 @@ class Build(models.Model): self.project.users.all().values_list('username', flat=True), ), pk=self.pk, - ) + ), ) def get_absolute_url(self): @@ -620,7 +624,7 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): """Build command for a ``Build``.""" build = models.ForeignKey( - Build, verbose_name=_('Build'), related_name='commands' + Build, verbose_name=_('Build'), related_name='commands', ) command = models.TextField(_('Command')) diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py index 2039aa0ec..ae7e5a8fb 100644 --- a/readthedocs/builds/querysets.py +++ b/readthedocs/builds/querysets.py @@ -38,7 +38,7 @@ class VersionQuerySetBase(models.QuerySet): def protected(self, user=None, project=None, only_active=True): queryset = self.filter( - privacy_level__in=[constants.PUBLIC, constants.PROTECTED] + privacy_level__in=[constants.PUBLIC, constants.PROTECTED], ) if user: queryset = self._add_user_repos(queryset, user) diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py index 69ed90dad..00f08ab2d 100644 --- a/readthedocs/builds/views.py +++ b/readthedocs/builds/views.py @@ -59,7 +59,7 @@ class BuildTriggerMixin: ) update_docs_task, build = trigger_build( - project=project, version=version + project=project, version=version, ) if (update_docs_task, build) == (None, None): # Build was skipped diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index a3a6e7877..50eba8b3e 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -90,7 +90,7 @@ class ConfigOptionNotSupportedError(ConfigError): ) super().__init__( template.format(self.configuration), - CONFIG_NOT_SUPPORTED + CONFIG_NOT_SUPPORTED, ) @@ -342,7 +342,7 @@ class BuildConfigV1(BuildConfigBase): if ':' not in build['image']: # Prepend proper image name to user's image name build['image'] = '{}:{}'.format( - DOCKER_DEFAULT_IMAGE, build['image'] + DOCKER_DEFAULT_IMAGE, build['image'], ) # Update docker default settings from image name if build['image'] in DOCKER_IMAGE_SETTINGS: @@ -378,14 +378,14 @@ class BuildConfigV1(BuildConfigBase): if 'use_system_site_packages' in raw_python: with self.catch_validation_error('python.use_system_site_packages'): python['use_system_site_packages'] = validate_bool( - raw_python['use_system_site_packages'] + raw_python['use_system_site_packages'], ) # Validate pip_install. if 'pip_install' in raw_python: with self.catch_validation_error('python.pip_install'): python['install_with_pip'] = validate_bool( - raw_python['pip_install'] + raw_python['pip_install'], ) # Validate extra_requirements. @@ -395,7 +395,7 @@ class BuildConfigV1(BuildConfigBase): self.error( 'python.extra_requirements', self.PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE, - code=PYTHON_INVALID + code=PYTHON_INVALID, ) if not python['install_with_pip']: python['extra_requirements'] = [] @@ -403,14 +403,14 @@ class BuildConfigV1(BuildConfigBase): for extra_name in raw_extra_requirements: with self.catch_validation_error('python.extra_requirements'): python['extra_requirements'].append( - validate_string(extra_name) + validate_string(extra_name), ) # Validate setup_py_install. if 'setup_py_install' in raw_python: with self.catch_validation_error('python.setup_py_install'): python['install_with_setup'] = validate_bool( - raw_python['setup_py_install'] + raw_python['setup_py_install'], ) if 'version' in raw_python: @@ -445,7 +445,7 @@ class BuildConfigV1(BuildConfigBase): if 'file' in raw_conda: with self.catch_validation_error('conda.file'): conda_environment = validate_file( - raw_conda['file'], self.base_path + raw_conda['file'], self.base_path, ) conda['environment'] = conda_environment @@ -683,7 +683,7 @@ class BuildConfigV2(BuildConfigBase): with self.catch_validation_error('python.extra_requirements'): extra_requirements = self.pop_config( - 'python.extra_requirements', [] + 'python.extra_requirements', [], ) extra_requirements = validate_list(extra_requirements) if extra_requirements and not python['install_with_pip']: @@ -801,7 +801,7 @@ class BuildConfigV2(BuildConfigBase): if not configuration: configuration = None configuration = self.pop_config( - 'sphinx.configuration', configuration + 'sphinx.configuration', configuration, ) if configuration is not None: configuration = validate_file(configuration, self.base_path) @@ -828,7 +828,7 @@ class BuildConfigV2(BuildConfigBase): if dashboard_doctype == 'mkdocs' or not self.sphinx: error_msg += ' but there is no "{}" key specified.'.format( - 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx' + 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx', ) else: error_msg += ' but your "sphinx.builder" key does not match.' diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 29fbac1be..98e579b1c 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -55,13 +55,15 @@ def get_build_config(config, env_config=None, source_file='readthedocs.yml'): ) -@pytest.mark.parametrize('files', [ - {'readthedocs.ymlmore': ''}, {'first': {'readthedocs.yml': ''}}, - {'startreadthedocs.yml': ''}, {'second': {'confuser.txt': 'content'}}, - {'noroot': {'readthedocs.ymlmore': ''}}, {'third': {'readthedocs.yml': 'content', 'Makefile': ''}}, - {'noroot': {'startreadthedocs.yml': ''}}, {'fourth': {'samplefile.yaml': 'content'}}, - {'readthebots.yaml': ''}, {'fifth': {'confuser.txt': '', 'readthedocs.yml': 'content'}}, -]) +@pytest.mark.parametrize( + 'files', [ + {'readthedocs.ymlmore': ''}, {'first': {'readthedocs.yml': ''}}, + {'startreadthedocs.yml': ''}, {'second': {'confuser.txt': 'content'}}, + {'noroot': {'readthedocs.ymlmore': ''}}, {'third': {'readthedocs.yml': 'content', 'Makefile': ''}}, + {'noroot': {'startreadthedocs.yml': ''}}, {'fourth': {'samplefile.yaml': 'content'}}, + {'readthebots.yaml': ''}, {'fifth': {'confuser.txt': '', 'readthedocs.yml': 'content'}}, + ], +) def test_load_no_config_file(tmpdir, files): apply_fs(tmpdir, files) base = str(tmpdir) @@ -71,9 +73,11 @@ def test_load_no_config_file(tmpdir, files): def test_load_empty_config_file(tmpdir): - apply_fs(tmpdir, { - 'readthedocs.yml': '' - }) + apply_fs( + tmpdir, { + 'readthedocs.yml': '', + }, + ) base = str(tmpdir) with raises(ConfigError): load(base, {}) @@ -87,33 +91,39 @@ def test_minimal_config(tmpdir): def test_load_version1(tmpdir): - apply_fs(tmpdir, { - 'readthedocs.yml': textwrap.dedent(''' + apply_fs( + tmpdir, { + 'readthedocs.yml': textwrap.dedent(''' version: 1 - ''') - }) + '''), + }, + ) base = str(tmpdir) build = load(base, {'allow_v2': True}) assert isinstance(build, BuildConfigV1) def test_load_version2(tmpdir): - apply_fs(tmpdir, { - 'readthedocs.yml': textwrap.dedent(''' + apply_fs( + tmpdir, { + 'readthedocs.yml': textwrap.dedent(''' version: 2 - ''') - }) + '''), + }, + ) base = str(tmpdir) build = load(base, {'allow_v2': True}) assert isinstance(build, BuildConfigV2) def test_load_unknow_version(tmpdir): - apply_fs(tmpdir, { - 'readthedocs.yml': textwrap.dedent(''' + apply_fs( + tmpdir, { + 'readthedocs.yml': textwrap.dedent(''' version: 9 - ''') - }) + '''), + }, + ) base = str(tmpdir) with raises(ConfigError) as excinfo: load(base, {'allow_v2': True}) @@ -122,14 +132,16 @@ def test_load_unknow_version(tmpdir): def test_yaml_extension(tmpdir): """Make sure loading the 'readthedocs' file with a 'yaml' extension.""" - apply_fs(tmpdir, { - 'readthedocs.yaml': textwrap.dedent( - ''' + apply_fs( + tmpdir, { + 'readthedocs.yaml': textwrap.dedent( + ''' python: version: 3 ''' - ), - }) + ), + }, + ) base = str(tmpdir) config = load(base, {}) assert isinstance(config, BuildConfigV1) @@ -142,13 +154,15 @@ def test_build_config_has_source_file(tmpdir): def test_build_config_has_list_with_single_empty_value(tmpdir): - base = str(apply_fs(tmpdir, { - 'readthedocs.yml': textwrap.dedent( - ''' + base = str(apply_fs( + tmpdir, { + 'readthedocs.yml': textwrap.dedent( + ''' formats: [] ''' - ) - })) + ), + }, + )) build = load(base, {}) assert isinstance(build, BuildConfigV1) assert build.formats == [] @@ -166,7 +180,7 @@ def test_doc_type(): 'defaults': { 'doctype': 'sphinx', }, - } + }, ) build.validate() assert build.doctype == 'sphinx' @@ -522,7 +536,8 @@ class TestValidateBuild: assert build.build.image == 'readthedocs/build:2.0' @pytest.mark.parametrize( - 'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest']) + 'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest'], + ) def test_it_priorities_image_from_env_config(self, tmpdir, image): apply_fs(tmpdir, yaml_config_dir) defaults = { @@ -642,9 +657,11 @@ def test_raise_config_not_supported(): assert excinfo.value.code == CONFIG_NOT_SUPPORTED -@pytest.mark.parametrize('correct_config_filename', - [prefix + 'readthedocs.' + extension for prefix in {'', '.'} - for extension in {'yml', 'yaml'}]) +@pytest.mark.parametrize( + 'correct_config_filename', + [prefix + 'readthedocs.' + extension for prefix in {'', '.'} + for extension in {'yml', 'yaml'}], +) def test_config_filenames_regex(correct_config_filename): assert re.match(CONFIG_FILENAME_REGEX, correct_config_filename) @@ -706,7 +723,8 @@ def test_as_dict(tmpdir): class TestBuildConfigV2: def get_build_config( - self, config, env_config=None, source_file='readthedocs.yml'): + self, config, env_config=None, source_file='readthedocs.yml', + ): return BuildConfigV2( env_config or {}, config, @@ -739,7 +757,7 @@ class TestBuildConfigV2: def test_formats_check_invalid_type(self): build = self.get_build_config( - {'formats': ['htmlzip', 'invalid', 'epub']} + {'formats': ['htmlzip', 'invalid', 'epub']}, ) with raises(InvalidConfig) as excinfo: build.validate() @@ -836,7 +854,8 @@ class TestBuildConfigV2: assert excinfo.value.key == 'build.image' @pytest.mark.parametrize( - 'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest']) + 'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest'], + ) def test_build_image_priorities_default(self, image): build = self.get_build_config( {'build': {'image': 'latest'}}, @@ -880,9 +899,13 @@ class TestBuildConfigV2: build.validate() assert excinfo.value.key == 'python' - @pytest.mark.parametrize('image,versions', - [('latest', [2, 2.7, 3, 3.5, 3.6]), - ('stable', [2, 2.7, 3, 3.5, 3.6])]) + @pytest.mark.parametrize( + 'image,versions', + [ + ('latest', [2, 2.7, 3, 3.5, 3.6]), + ('stable', [2, 2.7, 3, 3.5, 3.6]), + ], + ) def test_python_version(self, image, versions): for version in versions: build = self.get_build_config({ @@ -908,9 +931,13 @@ class TestBuildConfigV2: build.validate() assert build.python.version == 3.6 - @pytest.mark.parametrize('image,versions', - [('latest', [1, 2.8, 4, 3.8]), - ('stable', [1, 2.8, 4, 3.8])]) + @pytest.mark.parametrize( + 'image,versions', + [ + ('latest', [1, 2.8, 4, 3.8]), + ('stable', [1, 2.8, 4, 3.8]), + ], + ) def test_python_version_invalid(self, image, versions): for version in versions: build = self.get_build_config({ @@ -1086,7 +1113,7 @@ class TestBuildConfigV2: 'python': { 'install': 'pip', 'extra_requirements': ['docs', 'tests'], - } + }, }) build.validate() assert build.python.extra_requirements == ['docs', 'tests'] @@ -1095,7 +1122,7 @@ class TestBuildConfigV2: build = self.get_build_config({ 'python': { 'extra_requirements': ['docs', 'tests'], - } + }, }) with raises(InvalidConfig) as excinfo: build.validate() @@ -1106,7 +1133,7 @@ class TestBuildConfigV2: 'python': { 'install': 'setup.py', 'extra_requirements': ['docs', 'tests'], - } + }, }) with raises(InvalidConfig) as excinfo: build.validate() @@ -1202,10 +1229,14 @@ class TestBuildConfigV2: assert build.mkdocs is None assert build.doctype == 'sphinx' - @pytest.mark.parametrize('value,expected', - [('html', 'sphinx'), - ('htmldir', 'sphinx_htmldir'), - ('singlehtml', 'sphinx_singlehtml')]) + @pytest.mark.parametrize( + 'value,expected', + [ + ('html', 'sphinx'), + ('htmldir', 'sphinx_htmldir'), + ('singlehtml', 'sphinx_singlehtml'), + ], + ) def test_sphinx_builder_check_valid(self, value, expected): build = self.get_build_config( {'sphinx': {'builder': value}}, @@ -1228,7 +1259,8 @@ class TestBuildConfigV2: build.sphinx.builder == 'sphinx' @pytest.mark.skip( - 'This test is not compatible with the new validation around doctype.') + 'This test is not compatible with the new validation around doctype.', + ) def test_sphinx_builder_ignores_default(self): build = self.get_build_config( {}, @@ -1460,7 +1492,7 @@ class TestBuildConfigV2: def test_submodules_include_check_valid(self): build = self.get_build_config({ 'submodules': { - 'include': ['one', 'two'] + 'include': ['one', 'two'], }, }) build.validate() @@ -1493,8 +1525,8 @@ class TestBuildConfigV2: def test_submodules_exclude_check_valid(self): build = self.get_build_config({ 'submodules': { - 'exclude': ['one', 'two'] - } + 'exclude': ['one', 'two'], + }, }) build.validate() assert build.submodules.include == [] @@ -1594,26 +1626,28 @@ class TestBuildConfigV2: assert build.submodules.exclude == [] assert build.submodules.recursive is False - @pytest.mark.parametrize('value,key', [ - ({'typo': 'something'}, 'typo'), - ( - { - 'pyton': { - 'version': 'another typo', - } - }, - 'pyton.version' - ), - ( - { - 'build': { - 'image': 'latest', - 'extra': 'key', - } - }, - 'build.extra' - ) - ]) + @pytest.mark.parametrize( + 'value,key', [ + ({'typo': 'something'}, 'typo'), + ( + { + 'pyton': { + 'version': 'another typo', + }, + }, + 'pyton.version', + ), + ( + { + 'build': { + 'image': 'latest', + 'extra': 'key', + }, + }, + 'build.extra', + ), + ], + ) def test_strict_validation(self, value, key): build = self.get_build_config(value) with raises(InvalidConfig) as excinfo: @@ -1631,13 +1665,15 @@ class TestBuildConfigV2: build.validate() assert build.raw_config == {} - @pytest.mark.parametrize('value,expected', [ - ({}, []), - ({'one': 1}, ['one']), - ({'one': {'two': 3}}, ['one', 'two']), - (OrderedDict([('one', 1), ('two', 2)]), ['one']), - (OrderedDict([('one', {'two': 2}), ('three', 3)]), ['one', 'two']), - ]) + @pytest.mark.parametrize( + 'value,expected', [ + ({}, []), + ({'one': 1}, ['one']), + ({'one': {'two': 3}}, ['one', 'two']), + (OrderedDict([('one', 1), ('two', 2)]), ['one']), + (OrderedDict([('one', {'two': 2}), ('three', 3)]), ['one', 'two']), + ], + ) def test_get_extra_key(self, value, expected): build = self.get_build_config({}) assert build._get_extra_key(value) == expected diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py index 9062f32ca..64a7e1f72 100644 --- a/readthedocs/config/tests/test_parser.py +++ b/readthedocs/config/tests/test_parser.py @@ -64,6 +64,7 @@ base: other_path name: second nested: works: true - ''') + ''' + ) with raises(ParseError): parse(buf) diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py index 536c1fe46..467110e98 100644 --- a/readthedocs/core/context_processors.py +++ b/readthedocs/core/context_processors.py @@ -13,12 +13,12 @@ def readthedocs_processor(request): 'USE_SUBDOMAINS': getattr(settings, 'USE_SUBDOMAINS', None), 'GLOBAL_ANALYTICS_CODE': getattr(settings, 'GLOBAL_ANALYTICS_CODE'), 'DASHBOARD_ANALYTICS_CODE': getattr( - settings, 'DASHBOARD_ANALYTICS_CODE' + settings, 'DASHBOARD_ANALYTICS_CODE', ), 'SITE_ROOT': getattr(settings, 'SITE_ROOT', '') + '/', 'TEMPLATE_ROOT': getattr(settings, 'TEMPLATE_ROOT', '') + '/', 'DO_NOT_TRACK_ENABLED': getattr( - settings, 'DO_NOT_TRACK_ENABLED', False + settings, 'DO_NOT_TRACK_ENABLED', False, ), 'USE_PROMOS': getattr(settings, 'USE_PROMOS', False), } diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py index 14e87f7df..037b73198 100644 --- a/readthedocs/core/management/commands/archive.py +++ b/readthedocs/core/management/commands/archive.py @@ -33,6 +33,7 @@ class Command(BaseCommand): 'doc_index': doc_index, 'MEDIA_URL': settings.MEDIA_URL, } - html = template_loader.get_template('archive/index.html' - ).render(context) + html = template_loader.get_template( + 'archive/index.html', + ).render(context) print(html) diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py index e6f032824..d46b8cd10 100644 --- a/readthedocs/core/management/commands/clean_builds.py +++ b/readthedocs/core/management/commands/clean_builds.py @@ -40,7 +40,7 @@ class Command(BaseCommand): max_date = timezone.now() - timedelta(days=options['days']) queryset = ( Build.objects.values('project', 'version').annotate( - max_date=Max('date') + max_date=Max('date'), ).filter(max_date__lt=max_date).order_by('-max_date') ) for build in queryset: diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py index 6c8cb5b5c..90ce69e9e 100644 --- a/readthedocs/core/management/commands/import_github.py +++ b/readthedocs/core/management/commands/import_github.py @@ -15,6 +15,9 @@ class Command(BaseCommand): def handle(self, *args, **options): if args: for slug in args: - for service in GitHubService.for_user(User.objects.get(username=slug - ),): + for service in GitHubService.for_user( + User.objects.get( + username=slug, + ), + ): service.sync() diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index f0da49cdd..f3813f21e 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -37,8 +37,11 @@ class Command(BaseCommand): print('Invalid GitHub token, exiting') return - for project in Project.objects.filter(programming_language__in=[ - 'none', '', 'words'],).filter(repo__contains='github',): + for project in Project.objects.filter( + programming_language__in=[ + 'none', '', 'words', + ], + ).filter(repo__contains='github',): user = repo = '' repo_url = project.repo for regex in GITHUB_REGEXS: @@ -65,7 +68,7 @@ class Command(BaseCommand): if not languages: continue sorted_langs = sorted( - list(languages.items()), key=lambda x: x[1], reverse=True + list(languages.items()), key=lambda x: x[1], reverse=True, ) print('Sorted langs: %s ' % sorted_langs) top_lang = sorted_langs[0][0] @@ -74,8 +77,9 @@ class Command(BaseCommand): if top_lang in PL_DICT: slug = PL_DICT[top_lang] print('Setting {} to {}'.format(repo_url, slug)) - Project.objects.filter(pk=project.pk - ).update(programming_language=slug) + Project.objects.filter( + pk=project.pk, + ).update(programming_language=slug) else: print('Language unknown: %s' % top_lang) cache.set(cache_key, top_lang, 60 * 600) diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py index 33736b7a7..6fe4bb22e 100644 --- a/readthedocs/core/management/commands/set_metadata.py +++ b/readthedocs/core/management/commands/set_metadata.py @@ -24,7 +24,7 @@ class Command(BaseCommand): log.info('Generating metadata for %s', p) try: broadcast( - type='app', task=tasks.update_static_metadata, args=[p.pk] + type='app', task=tasks.update_static_metadata, args=[p.pk], ) except Exception: log.exception('Build failed for %s', p) diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py index 41719354c..2d843b9ba 100644 --- a/readthedocs/core/management/commands/symlink.py +++ b/readthedocs/core/management/commands/symlink.py @@ -25,8 +25,9 @@ class Command(BaseCommand): if 'all' in projects: pks = Project.objects.values_list('pk', flat=True) else: - pks = Project.objects.filter(slug__in=projects - ).values_list('pk', flat=True) + pks = Project.objects.filter( + slug__in=projects, + ).values_list('pk', flat=True) for proj in pks: try: tasks.symlink_project(project_pk=proj) diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py index c40dbfd0f..6fc5bbe55 100644 --- a/readthedocs/core/middleware.py +++ b/readthedocs/core/middleware.py @@ -68,7 +68,8 @@ class SubdomainMiddleware(MiddlewareMixin): subdomain = domain_parts[0] is_www = subdomain.lower() == 'www' if not is_www and ( # Support ports during local dev - public_domain in host or public_domain in full_host): + public_domain in host or public_domain in full_host + ): if not Project.objects.filter(slug=subdomain).exists(): raise Http404(_('Project not found')) request.subdomain = True @@ -77,8 +78,10 @@ class SubdomainMiddleware(MiddlewareMixin): return None # Serve CNAMEs - if (public_domain not in host and production_domain not in host and - 'localhost' not in host and 'testserver' not in host): + if ( + public_domain not in host and production_domain not in host and + 'localhost' not in host and 'testserver' not in host + ): request.cname = True domains = Domain.objects.filter(domain=host) if domains.count(): @@ -91,11 +94,13 @@ class SubdomainMiddleware(MiddlewareMixin): LOG_TEMPLATE.format( msg='Domain Object Detected: %s' % domain.domain, **log_kwargs - ) + ), ) break - if (not hasattr(request, 'domain_object') and - 'HTTP_X_RTD_SLUG' in request.META): + if ( + not hasattr(request, 'domain_object') and + 'HTTP_X_RTD_SLUG' in request.META + ): request.slug = request.META['HTTP_X_RTD_SLUG'].lower() request.urlconf = SUBDOMAIN_URLCONF request.rtdheader = True @@ -103,7 +108,7 @@ class SubdomainMiddleware(MiddlewareMixin): LOG_TEMPLATE.format( msg='X-RTD-Slug header detected: %s' % request.slug, **log_kwargs - ) + ), ) # Try header first, then DNS elif not hasattr(request, 'domain_object'): @@ -117,7 +122,7 @@ class SubdomainMiddleware(MiddlewareMixin): LOG_TEMPLATE.format( msg='CNAME cached: {}->{}'.format(slug, host), **log_kwargs - ) + ), ) request.slug = slug request.urlconf = SUBDOMAIN_URLCONF @@ -125,12 +130,12 @@ class SubdomainMiddleware(MiddlewareMixin): LOG_TEMPLATE.format( msg='CNAME detected: %s' % request.slug, **log_kwargs - ) + ), ) except: # noqa # Some crazy person is CNAMEing to us. 404. log.warning( - LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs) + LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs), ) raise Http404(_('Invalid hostname')) # Google was finding crazy www.blah.readthedocs.org domains. @@ -139,12 +144,12 @@ class SubdomainMiddleware(MiddlewareMixin): # Stop www.fooo.readthedocs.org if domain_parts[0] == 'www': log.debug( - LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs) + LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs), ) return HttpResponseBadRequest(_('Invalid hostname')) log.debug( LOG_TEMPLATE - .format(msg='Allowing long domain name', **log_kwargs) + .format(msg='Allowing long domain name', **log_kwargs), ) # raise Http404(_('Invalid hostname')) # Normal request. @@ -248,13 +253,15 @@ class FooterNoSessionMiddleware(SessionMiddleware): """ IGNORE_URLS = [ - '/api/v2/footer_html', '/sustainability/view', '/sustainability/click' + '/api/v2/footer_html', '/sustainability/view', '/sustainability/click', ] def process_request(self, request): for url in self.IGNORE_URLS: - if (request.path_info.startswith(url) and - settings.SESSION_COOKIE_NAME not in request.COOKIES): + if ( + request.path_info.startswith(url) and + settings.SESSION_COOKIE_NAME not in request.COOKIES + ): # Hack request.session otherwise the Authentication middleware complains. request.session = {} return @@ -262,7 +269,9 @@ class FooterNoSessionMiddleware(SessionMiddleware): def process_response(self, request, response): for url in self.IGNORE_URLS: - if (request.path_info.startswith(url) and - settings.SESSION_COOKIE_NAME not in request.COOKIES): + if ( + request.path_info.startswith(url) and + settings.SESSION_COOKIE_NAME not in request.COOKIES + ): return response return super().process_response(request, response) diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index 63ac39d10..91313ff2a 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -48,7 +48,7 @@ class UserProfile(models.Model): def get_absolute_url(self): return reverse( - 'profiles_profile_detail', kwargs={'username': self.user.username} + 'profiles_profile_detail', kwargs={'username': self.user.username}, ) def get_contribution_details(self): diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index 389fedc98..f66910a72 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -246,7 +246,7 @@ class ResolverBase: private = version.privacy_level == PRIVATE except Version.DoesNotExist: private = getattr( - settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC + settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC, ) == PRIVATE return private diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index 3de4c2b37..67e0cd47a 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -101,7 +101,7 @@ class Symlink: project=self.project.slug, version='', msg='Removing single version symlink', - ) + ), ) safe_unlink(self.project_root) safe_makedirs(self.project_root) @@ -190,7 +190,7 @@ class Symlink: project=self.project.slug, version='', msg=log_msg, - ) + ), ) symlink = os.path.join(self.CNAME_ROOT, domain.domain) safe_unlink(symlink) @@ -217,14 +217,14 @@ class Symlink: subprojects.add(rel.alias) for from_slug, to_slug in list(from_to.items()): log_msg = 'Symlinking subproject: {} -> {}'.format( - from_slug, to_slug + from_slug, to_slug, ) log.info( constants.LOG_TEMPLATE.format( project=self.project.slug, version='', msg=log_msg, - ) + ), ) symlink = os.path.join(self.subproject_root, from_slug) docs_dir = os.path.join( @@ -378,13 +378,14 @@ class PublicSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'public_web_root') PROJECT_CNAME_ROOT = os.path.join( - settings.SITE_ROOT, 'public_cname_project' + settings.SITE_ROOT, 'public_cname_project', ) def get_version_queryset(self): return ( - self.project.versions.protected(only_active=False - ).filter(built=True) | + self.project.versions.protected( + only_active=False, + ).filter(built=True) | self.project.versions.protected(only_active=True) ) @@ -399,7 +400,7 @@ class PrivateSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'private_web_root') PROJECT_CNAME_ROOT = os.path.join( - settings.SITE_ROOT, 'private_cname_project' + settings.SITE_ROOT, 'private_cname_project', ) def get_version_queryset(self): diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py index e4f3e329c..7c1b86f47 100644 --- a/readthedocs/core/tasks.py +++ b/readthedocs/core/tasks.py @@ -67,6 +67,6 @@ def clear_persistent_messages(): # Delete all expired message_extend's messages log.info("Deleting all expired message_extend's messages") expired_messages = PersistentMessage.objects.filter( - expires__lt=timezone.now() + expires__lt=timezone.now(), ) expired_messages.delete() diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index 3df7c5769..a94e511ac 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -61,7 +61,7 @@ def restructuredtext(value, short=False): 'file_insertion_enabled': False, } docutils_settings.update( - getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {}) + getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {}), ) try: parts = publish_parts( diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index e81a6f3a3..f105a300e 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -19,7 +19,7 @@ def is_admin(user, project): @register.simple_tag(takes_context=True) def get_public_projects(context, user): projects = Project.objects.for_user_and_viewer( - user=user, viewer=context['request'].user + user=user, viewer=context['request'].user, ) context['public_projects'] = projects return '' diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py index dedc458e9..545617a55 100644 --- a/readthedocs/core/urls/single_version.py +++ b/readthedocs/core/urls/single_version.py @@ -37,7 +37,7 @@ groups = [single_version_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): groups.insert( - 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), ) # Allow `/docs/` URL's when not using subdomains or during local dev diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 506688b75..90f3b1865 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -28,15 +28,19 @@ subdomain_urls = [ r'page/(?P.*)$'.format(**pattern_opts), redirect_page_with_filename, name='docs_detail', ), - url((r'^(?:|projects/(?P{project_slug})/)$').format( - **pattern_opts - ), redirect_project_slug, name='redirect_project_slug',), - url(( - r'^(?:|projects/(?P{project_slug})/)' - r'(?P{lang_slug})/' - r'(?P{version_slug})/' - r'(?P{filename_slug})$'.format(**pattern_opts) - ), serve_docs, name='docs_detail',), + url( + (r'^(?:|projects/(?P{project_slug})/)$').format( + **pattern_opts + ), redirect_project_slug, name='redirect_project_slug', + ), + url( + ( + r'^(?:|projects/(?P{project_slug})/)' + r'(?P{lang_slug})/' + r'(?P{version_slug})/' + r'(?P{filename_slug})$'.format(**pattern_opts) + ), serve_docs, name='docs_detail', + ), ] groups = [subdomain_urls] diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 6302e9302..162ff932c 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -67,7 +67,7 @@ def build_branches(project, branch_list): versions = project.versions_from_branch_name(branch) for version in versions: log.info( - '(Branch Build) Processing %s:%s', project.slug, version.slug + '(Branch Build) Processing %s:%s', project.slug, version.slug, ) ret = _build_version(project, version.slug, already_built=to_build) if ret: @@ -151,7 +151,7 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: msg = '(URL Build) Build Started: {} [{}]'.format( - url, ' '.join(built) + url, ' '.join(built), ) log_info(project_slug, msg=msg) ret += msg @@ -159,7 +159,7 @@ def _build_url(url, projects, branches): for project_slug, not_building in list(all_not_building.items()): if not_building: msg = '(URL Build) Not Building: {} [{}]'.format( - url, ' '.join(not_building) + url, ' '.join(not_building), ) log_info(project_slug, msg=msg) ret += msg @@ -296,7 +296,7 @@ def bitbucket_build(request): if not repository['absolute_url']: return HttpResponse('Invalid request', status=400) search_url = 'bitbucket.org{}'.format( - repository['absolute_url'].rstrip('/') + repository['absolute_url'].rstrip('/'), ) elif version == 2: changes = data['push']['changes'] @@ -304,7 +304,7 @@ def bitbucket_build(request): if not data['repository']['full_name']: return HttpResponse('Invalid request', status=400) search_url = 'bitbucket.org/{}'.format( - data['repository']['full_name'] + data['repository']['full_name'], ) except (TypeError, ValueError, KeyError): log.exception('Invalid Bitbucket webhook payload') @@ -353,10 +353,10 @@ def generic_build(request, project_id_or_slug=None): except (Project.DoesNotExist, ValueError): log.exception( '(Incoming Generic Build) Repo not found: %s', - project_id_or_slug + project_id_or_slug, ) return HttpResponseNotFound( - 'Repo not found: %s' % project_id_or_slug + 'Repo not found: %s' % project_id_or_slug, ) # This endpoint doesn't require authorization, we shouldn't allow builds to # be triggered from this any longer. Deprecation plan is to selectively diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 388c8ce76..46a6c8ec9 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -115,7 +115,7 @@ def redirect_project_slug(request, project, subproject): # pylint: disable=unus def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa """Redirect /page/file.html to /en/latest/file.html.""" return HttpResponseRedirect( - resolve(subproject or project, filename=filename) + resolve(subproject or project, filename=filename), ) @@ -134,7 +134,7 @@ def _serve_file(request, filename, basepath): # Serve from Nginx content_type, encoding = mimetypes.guess_type( - os.path.join(basepath, filename) + os.path.join(basepath, filename), ) content_type = content_type or 'application/octet-stream' response = HttpResponse(content_type=content_type) @@ -162,7 +162,7 @@ def _serve_file(request, filename, basepath): @map_subproject_slug def serve_docs( request, project, subproject, lang_slug=None, version_slug=None, - filename='' + filename='', ): """Map existing proj, lang, version, filename views to the file format.""" if not version_slug: @@ -229,7 +229,7 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''): files_tried.append(os.path.join(basepath, filename)) raise Http404( - 'File not found. Tried these files: %s' % ','.join(files_tried) + 'File not found. Tried these files: %s' % ','.join(files_tried), ) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index ba5d010b4..8d80ecc35 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -97,7 +97,7 @@ class BaseMkdocs(BaseBuilder): if hasattr(exc, 'problem_mark'): mark = exc.problem_mark note = ' (line %d, column %d)' % ( - mark.line + 1, mark.column + 1 + mark.line + 1, mark.column + 1, ) raise MkDocsYAMLParseError( 'Your mkdocs.yml could not be loaded, ' @@ -209,12 +209,12 @@ class BaseMkdocs(BaseBuilder): 'docroot': docs_dir, 'source_suffix': '.md', 'api_host': getattr( - settings, 'PUBLIC_API_URL', 'https://readthedocs.org' + settings, 'PUBLIC_API_URL', 'https://readthedocs.org', ), 'ad_free': not self.project.show_advertising, 'commit': self.version.project.vcs_repo(self.version.slug).commit, 'global_analytics_code': getattr( - settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1' + settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1', ), 'user_analytics_code': analytics_code, } diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py index 1abacebba..4f6deeb61 100644 --- a/readthedocs/doc_builder/constants.py +++ b/readthedocs/doc_builder/constants.py @@ -33,7 +33,7 @@ DOCKER_IMAGE_SETTINGS = getattr(settings, 'DOCKER_IMAGE_SETTINGS', {}) old_config = getattr(settings, 'DOCKER_BUILD_IMAGES', None) if old_config: log.warning( - 'Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS' + 'Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS', ) DOCKER_IMAGE_SETTINGS.update(old_config) diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index 70a716e68..cb27a88dd 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -315,8 +315,10 @@ class DockerBuildCommand(BuildCommand): killed_in_output = 'Killed' in '\n'.join( self.output.splitlines()[-15:], ) - if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and - killed_in_output): + if self.exit_code == DOCKER_OOM_EXIT_CODE or ( + self.exit_code == 1 and + killed_in_output + ): self.output += str( _( '\n\nCommand killed due to excessive memory consumption\n', @@ -975,11 +977,13 @@ class DockerBuildEnvironment(BuildEnvironment): _('Build exited due to excessive memory consumption'), ) elif state.get('Error'): - self.failure = BuildEnvironmentError(( - _('Build exited due to unknown error: {0}').format( - state.get('Error'), - ) - ),) + self.failure = BuildEnvironmentError( + ( + _('Build exited due to unknown error: {0}').format( + state.get('Error'), + ) + ), + ) def create_container(self): """Create docker container.""" diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index afe35dce5..dff39908d 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -11,7 +11,7 @@ class BuildEnvironmentException(Exception): def __init__(self, message=None, **kwargs): self.status_code = kwargs.pop( - 'status_code', None + 'status_code', None, ) or self.status_code or 1 message = message or self.get_default_message() super().__init__(message, **kwargs) diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index 730f29296..b60b58e0b 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -33,7 +33,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm): widget=forms.HiddenInput( attrs={ 'data-bind': 'valueInit: last_4_card_digits, value: last_4_card_digits', - } + }, ), ) @@ -120,5 +120,5 @@ class GoldProjectForm(forms.Form): return cleaned_data self.add_error( - None, 'You already have the max number of supported projects.' + None, 'You already have the max number of supported projects.', ) diff --git a/readthedocs/gold/tests/test_forms.py b/readthedocs/gold/tests/test_forms.py index 4231c713d..c379bba3b 100644 --- a/readthedocs/gold/tests/test_forms.py +++ b/readthedocs/gold/tests/test_forms.py @@ -44,7 +44,7 @@ class GoldSubscriptionFormTests(TestCase): 'id': 'cus_12345', 'description': self.user.get_full_name(), 'email': self.user.email, - 'subscriptions': subscription_list + 'subscriptions': subscription_list, } subscription_obj = { 'id': 'sub_12345', @@ -56,7 +56,7 @@ class GoldSubscriptionFormTests(TestCase): 'amount': 1000, 'currency': 'usd', 'name': 'Test', - } + }, } self.mock_request([ (customer_obj, ''), @@ -65,13 +65,14 @@ class GoldSubscriptionFormTests(TestCase): ]) # Create user and subscription - subscription_form = GoldSubscriptionForm({ - 'level': 'v1-org-5', - 'last_4_card_digits': '0000', - 'stripe_token': 'GARYBUSEY', - 'business_vat_id': 'business-vat-id', - }, - customer=self.user, + subscription_form = GoldSubscriptionForm( + { + 'level': 'v1-org-5', + 'last_4_card_digits': '0000', + 'stripe_token': 'GARYBUSEY', + 'business_vat_id': 'business-vat-id', + }, + customer=self.user, ) self.assertTrue(subscription_form.is_valid()) subscription = subscription_form.save() @@ -83,18 +84,24 @@ class GoldSubscriptionFormTests(TestCase): self.assertEqual(self.user.gold.first().level, 'v1-org-5') self.mocks['request'].request.assert_has_calls([ - mock.call('post', - '/v1/customers', - {'description': mock.ANY, 'email': mock.ANY, 'business_vat_id': 'business-vat-id'}, - mock.ANY), - mock.call('get', - '/v1/customers/cus_12345/subscriptions', - mock.ANY, - mock.ANY), - mock.call('post', - '/v1/customers/cus_12345/subscriptions', - {'source': mock.ANY, 'plan': 'v1-org-5'}, - mock.ANY), + mock.call( + 'post', + '/v1/customers', + {'description': mock.ANY, 'email': mock.ANY, 'business_vat_id': 'business-vat-id'}, + mock.ANY, + ), + mock.call( + 'get', + '/v1/customers/cus_12345/subscriptions', + mock.ANY, + mock.ANY, + ), + mock.call( + 'post', + '/v1/customers/cus_12345/subscriptions', + {'source': mock.ANY, 'plan': 'v1-org-5'}, + mock.ANY, + ), ]) def test_add_subscription_update_user(self): @@ -110,7 +117,7 @@ class GoldSubscriptionFormTests(TestCase): 'id': 'cus_12345', 'description': self.user.get_full_name(), 'email': self.user.email, - 'subscriptions': subscription_list + 'subscriptions': subscription_list, } subscription_obj = { 'id': 'sub_12345', @@ -122,7 +129,7 @@ class GoldSubscriptionFormTests(TestCase): 'amount': 1000, 'currency': 'usd', 'name': 'Test', - } + }, } self.mock_request([ (customer_obj, ''), @@ -134,11 +141,13 @@ class GoldSubscriptionFormTests(TestCase): # Create user and update the current gold subscription golduser = fixture.get(GoldUser, user=self.user, stripe_id='cus_12345') subscription_form = GoldSubscriptionForm( - {'level': 'v1-org-5', - 'last_4_card_digits': '0000', - 'stripe_token': 'GARYBUSEY'}, + { + 'level': 'v1-org-5', + 'last_4_card_digits': '0000', + 'stripe_token': 'GARYBUSEY', + }, customer=self.user, - instance=golduser + instance=golduser, ) self.assertTrue(subscription_form.is_valid()) subscription = subscription_form.save() @@ -149,22 +158,30 @@ class GoldSubscriptionFormTests(TestCase): self.assertEqual(self.user.gold.first().level, 'v1-org-5') self.mocks['request'].request.assert_has_calls([ - mock.call('get', - '/v1/customers/cus_12345', - {}, - mock.ANY), - mock.call('post', - '/v1/customers/cus_12345', - {'description': mock.ANY, 'email': mock.ANY}, - mock.ANY), - mock.call('get', - '/v1/customers/cus_12345/subscriptions', - mock.ANY, - mock.ANY), - mock.call('post', - '/v1/customers/cus_12345/subscriptions', - {'source': mock.ANY, 'plan': 'v1-org-5'}, - mock.ANY), + mock.call( + 'get', + '/v1/customers/cus_12345', + {}, + mock.ANY, + ), + mock.call( + 'post', + '/v1/customers/cus_12345', + {'description': mock.ANY, 'email': mock.ANY}, + mock.ANY, + ), + mock.call( + 'get', + '/v1/customers/cus_12345/subscriptions', + mock.ANY, + mock.ANY, + ), + mock.call( + 'post', + '/v1/customers/cus_12345/subscriptions', + {'source': mock.ANY, 'plan': 'v1-org-5'}, + mock.ANY, + ), ]) def test_update_subscription_plan(self): @@ -179,7 +196,7 @@ class GoldSubscriptionFormTests(TestCase): 'amount': 1000, 'currency': 'usd', 'name': 'Test', - } + }, } subscription_list = { 'object': 'list', @@ -192,7 +209,7 @@ class GoldSubscriptionFormTests(TestCase): 'id': 'cus_12345', 'description': self.user.get_full_name(), 'email': self.user.email, - 'subscriptions': subscription_list + 'subscriptions': subscription_list, } self.mock_request([ (customer_obj, ''), @@ -200,10 +217,12 @@ class GoldSubscriptionFormTests(TestCase): (subscription_obj, ''), ]) subscription_form = GoldSubscriptionForm( - {'level': 'v1-org-5', - 'last_4_card_digits': '0000', - 'stripe_token': 'GARYBUSEY'}, - customer=self.user + { + 'level': 'v1-org-5', + 'last_4_card_digits': '0000', + 'stripe_token': 'GARYBUSEY', + }, + customer=self.user, ) self.assertTrue(subscription_form.is_valid()) subscription = subscription_form.save() @@ -213,16 +232,22 @@ class GoldSubscriptionFormTests(TestCase): self.assertEqual(self.user.gold.first().level, 'v1-org-5') self.mocks['request'].request.assert_has_calls([ - mock.call('post', - '/v1/customers', - {'description': mock.ANY, 'email': mock.ANY}, - mock.ANY), - mock.call('get', - '/v1/customers/cus_12345/subscriptions', - mock.ANY, - mock.ANY), - mock.call('post', - '/v1/subscriptions/sub_12345', - {'source': mock.ANY, 'plan': 'v1-org-5'}, - mock.ANY), + mock.call( + 'post', + '/v1/customers', + {'description': mock.ANY, 'email': mock.ANY}, + mock.ANY, + ), + mock.call( + 'get', + '/v1/customers/cus_12345/subscriptions', + mock.ANY, + mock.ANY, + ), + mock.call( + 'post', + '/v1/subscriptions/sub_12345', + {'source': mock.ANY, 'plan': 'v1-org-5'}, + mock.ANY, + ), ]) diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py index 2a6b2047b..7f679686f 100644 --- a/readthedocs/gold/urls.py +++ b/readthedocs/gold/urls.py @@ -16,13 +16,13 @@ urlpatterns = [ name='gold_subscription', ), url( - r'^cancel/$', views.DeleteGoldSubscription.as_view(), name='gold_cancel' + r'^cancel/$', views.DeleteGoldSubscription.as_view(), name='gold_cancel', ), url(r'^projects/$', views.projects, name='gold_projects'), url( ( r'^projects/remove/(?P{project_slug})/$'.format( - project_slug=PROJECT_SLUG_REGEX + project_slug=PROJECT_SLUG_REGEX, ) ), views.projects_remove, diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py index 9b3286473..1b27ed8f5 100644 --- a/readthedocs/integrations/admin.py +++ b/readthedocs/integrations/admin.py @@ -24,7 +24,7 @@ def pretty_json_field(field, description, include_styles=False): 'float: left;', obj.formatted_json(field), styles, - ) + ), ) inner.short_description = description @@ -103,7 +103,7 @@ class IntegrationAdmin(admin.ModelAdmin): 'admin:{}_{}_changelist'.format( HttpExchange._meta.app_label, # pylint: disable=protected-access HttpExchange._meta.model_name, # pylint: disable=protected-access - ) + ), ) return mark_safe( '{} HTTP transactions'.format( @@ -111,7 +111,7 @@ class IntegrationAdmin(admin.ModelAdmin): 'integrations', obj.pk, obj.exchanges.count(), - ) + ), ) exchanges.short_description = 'HTTP exchanges' diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py index 83b68b2cf..a5ed64e96 100644 --- a/readthedocs/notifications/forms.py +++ b/readthedocs/notifications/forms.py @@ -31,8 +31,10 @@ class SendNotificationForm(forms.Form): def __init__(self, *args, **kwargs): self.notification_classes = kwargs.pop('notification_classes', []) super().__init__(*args, **kwargs) - self.fields['source'].choices = [(cls.name, cls.name) - for cls in self.notification_classes] + self.fields['source'].choices = [ + (cls.name, cls.name) + for cls in self.notification_classes + ] def clean_source(self): """Get the source class from the class name.""" diff --git a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py index c79a997f1..5b00b8377 100644 --- a/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py +++ b/readthedocs/oauth/migrations/0004_drop_github_and_bitbucket_models.py @@ -7,8 +7,10 @@ def forwards_remove_content_types(apps, schema_editor): ContentType = apps.get_model('contenttypes', 'ContentType') ContentType.objects.using(db).filter( app_label='oauth', - model__in=['githubproject', 'githuborganization', - 'bitbucketproject', 'bitbucketteam'] + model__in=[ + 'githubproject', 'githuborganization', + 'bitbucketproject', 'bitbucketteam', + ], ).delete() diff --git a/readthedocs/oauth/migrations/0006_move_oauth_source.py b/readthedocs/oauth/migrations/0006_move_oauth_source.py index 341f7ef44..8689b134f 100644 --- a/readthedocs/oauth/migrations/0006_move_oauth_source.py +++ b/readthedocs/oauth/migrations/0006_move_oauth_source.py @@ -46,8 +46,12 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(forwards_move_repo_source, - backwards_move_repo_source), - migrations.RunPython(forwards_move_org_source, - backwards_move_org_source), + migrations.RunPython( + forwards_move_repo_source, + backwards_move_repo_source, + ), + migrations.RunPython( + forwards_move_org_source, + backwards_move_org_source, + ), ] diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py index 40d224df6..65522c2bc 100644 --- a/readthedocs/oauth/models.py +++ b/readthedocs/oauth/models.py @@ -189,12 +189,14 @@ class RemoteRepository(models.Model): .filter(Q(repo=self.clone_url) | Q(repo__iendswith=fuzzy_url) | Q(repo__iendswith=fuzzy_url + '.git'))) # yapf: disable - return [{ - 'id': project.slug, - 'url': reverse( - 'projects_detail', - kwargs={ - 'project_slug': project.slug, - }, - ), - } for project in projects] + return [ + { + 'id': project.slug, + 'url': reverse( + 'projects_detail', + kwargs={ + 'project_slug': project.slug, + }, + ), + } for project in projects + ] diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py index e267e5afe..817ea98bd 100644 --- a/readthedocs/oauth/services/bitbucket.py +++ b/readthedocs/oauth/services/bitbucket.py @@ -190,7 +190,7 @@ class BitbucketService(Service): """Get webhook JSON data to post to the API.""" return json.dumps({ 'description': 'Read the Docs ({domain})'.format( - domain=settings.PRODUCTION_DOMAIN + domain=settings.PRODUCTION_DOMAIN, ), 'url': 'https://{domain}{path}'.format( domain=settings.PRODUCTION_DOMAIN, diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py index d313c9e55..59f9bce97 100644 --- a/readthedocs/payments/forms.py +++ b/readthedocs/payments/forms.py @@ -88,7 +88,7 @@ class StripeModelForm(forms.ModelForm): widget=forms.HiddenInput( attrs={ 'data-bind': 'valueInit: stripe_token', - } + }, ), ) @@ -103,7 +103,7 @@ class StripeModelForm(forms.ModelForm): 'textInput: cc_number, ' '''css: {'field-error': error_cc_number() != null}''' ), - } + }, ), max_length=25, required=False, @@ -117,7 +117,7 @@ class StripeModelForm(forms.ModelForm): 'textInput: cc_expiry, ' '''css: {'field-error': error_cc_expiry() != null}''' ), - } + }, ), max_length=10, required=False, @@ -132,7 +132,7 @@ class StripeModelForm(forms.ModelForm): '''css: {'field-error': error_cc_cvv() != null}''' ), 'autocomplete': 'off', - } + }, ), max_length=8, required=False, @@ -211,7 +211,7 @@ class StripeModelForm(forms.ModelForm): self.data['stripe_token'] = None except AttributeError: raise AttributeError( - 'Form was passed immutable QueryDict POST data' + 'Form was passed immutable QueryDict POST data', ) def fields_with_cc_group(self): diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 324e49864..165cf7491 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -135,7 +135,7 @@ class ProjectAdmin(GuardedModelAdmin): def send_owner_email(self, request, queryset): view = ProjectSendNotificationView.as_view( - action_name='send_owner_email' + action_name='send_owner_email', ) return view(request, queryset=queryset) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index fe9e9d801..115819060 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -122,7 +122,8 @@ class ProjectBasicsForm(ProjectForm): potential_slug = slugify(name) if Project.objects.filter(slug=potential_slug).exists(): raise forms.ValidationError( - _('Invalid project name, a project already exists with that name')) # yapf: disable # noqa + _('Invalid project name, a project already exists with that name'), + ) # yapf: disable # noqa if not potential_slug: # Check the generated slug won't be empty raise forms.ValidationError(_('Invalid project name'),) @@ -180,8 +181,8 @@ class ProjectExtraForm(ProjectForm): if len(tag) > 100: raise forms.ValidationError( _( - 'Length of each tag must be less than or equal to 100 characters.' - ) + 'Length of each tag must be less than or equal to 100 characters.', + ), ) return tags @@ -195,7 +196,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): initial='python', help_text=_( 'The Python interpreter used to create the virtual ' - 'environment.' + 'environment.', ), ) @@ -227,28 +228,33 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): default_choice = (None, '-' * 9) all_versions = self.instance.versions.values_list( - 'identifier', 'verbose_name' + 'identifier', 'verbose_name', ) self.fields['default_branch'].widget = forms.Select( - choices=[default_choice] + list(all_versions) + choices=[default_choice] + list(all_versions), ) active_versions = self.instance.all_active_versions().values_list('slug', 'verbose_name') # yapf: disabled self.fields['default_version'].widget = forms.Select( - choices=active_versions + choices=active_versions, ) def clean_conf_py_file(self): filename = self.cleaned_data.get('conf_py_file', '').strip() if filename and 'conf.py' not in filename: raise forms.ValidationError( - _('Your configuration file is invalid, make sure it contains ' - 'conf.py in it.')) # yapf: disable + _( + 'Your configuration file is invalid, make sure it contains ' + 'conf.py in it.', + ), + ) # yapf: disable return filename -class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, - ProjectExtraForm): +class UpdateProjectForm( + ProjectTriggerBuildMixin, ProjectBasicsForm, + ProjectExtraForm, +): class Meta: model = Project @@ -272,17 +278,17 @@ class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, if project: msg = _( 'There is already a "{lang}" translation ' - 'for the {proj} project.' + 'for the {proj} project.', ) if project.translations.filter(language=language).exists(): raise forms.ValidationError( - msg.format(lang=language, proj=project.slug) + msg.format(lang=language, proj=project.slug), ) main_project = project.main_language_project if main_project: if main_project.language == language: raise forms.ValidationError( - msg.format(lang=language, proj=main_project.slug) + msg.format(lang=language, proj=main_project.slug), ) siblings = ( main_project.translations @@ -292,7 +298,7 @@ class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm, ) if siblings: raise forms.ValidationError( - msg.format(lang=language, proj=main_project.slug) + msg.format(lang=language, proj=main_project.slug), ) return language @@ -323,7 +329,7 @@ class ProjectRelationshipBaseForm(forms.ModelForm): # This validation error is mostly for testing, users shouldn't see # this in normal circumstances raise forms.ValidationError( - _('Subproject nesting is not supported') + _('Subproject nesting is not supported'), ) return self.project @@ -331,7 +337,7 @@ class ProjectRelationshipBaseForm(forms.ModelForm): child = self.cleaned_data['child'] if child == self.project: raise forms.ValidationError( - _('A project can not be a subproject of itself') + _('A project can not be a subproject of itself'), ) return child @@ -346,7 +352,8 @@ class ProjectRelationshipBaseForm(forms.ModelForm): Project.objects.for_admin_user(self.user) .exclude(subprojects__isnull=False) .exclude(superprojects__isnull=False) - .exclude(pk=self.project.pk)) + .exclude(pk=self.project.pk) + ) return queryset @@ -452,7 +459,7 @@ def build_versions_form(project): class BaseUploadHTMLForm(forms.Form): content = forms.FileField(label=_('Zip file of HTML')) overwrite = forms.BooleanField( - required=False, label=_('Overwrite existing HTML?') + required=False, label=_('Overwrite existing HTML?'), ) def __init__(self, *args, **kwargs): @@ -504,7 +511,7 @@ class UserForm(forms.Form): user_qs = User.objects.filter(username=name) if not user_qs.exists(): raise forms.ValidationError( - _('User {name} does not exist').format(name=name) + _('User {name} does not exist').format(name=name), ) self.user = user_qs[0] return name @@ -528,7 +535,7 @@ class EmailHookForm(forms.Form): def clean_email(self): self.email = EmailHook.objects.get_or_create( - email=self.cleaned_data['email'], project=self.project + email=self.cleaned_data['email'], project=self.project, )[0] return self.email @@ -547,7 +554,7 @@ class WebHookForm(forms.ModelForm): def save(self, commit=True): self.webhook = WebHook.objects.get_or_create( - url=self.cleaned_data['url'], project=self.project + url=self.cleaned_data['url'], project=self.project, )[0] self.project.webhook_notifications.add(self.webhook) return self.project @@ -571,8 +578,11 @@ class TranslationBaseForm(forms.Form): def get_choices(self): return [ - (project.slug, '{project} ({lang})'.format( - project=project.slug, lang=project.get_language_display())) + ( + project.slug, '{project} ({lang})'.format( + project=project.slug, lang=project.get_language_display(), + ), + ) for project in self.get_translation_queryset().all() ] @@ -583,22 +593,22 @@ class TranslationBaseForm(forms.Form): if self.parent.main_language_project is not None: msg = 'Project "{project}" is already a translation' raise forms.ValidationError( - (_(msg).format(project=self.parent.slug)) + (_(msg).format(project=self.parent.slug)), ) project_translation_qs = self.get_translation_queryset().filter( - slug=translation_project_slug + slug=translation_project_slug, ) if not project_translation_qs.exists(): msg = 'Project "{project}" does not exist.' raise forms.ValidationError( - (_(msg).format(project=translation_project_slug)) + (_(msg).format(project=translation_project_slug)), ) self.translation = project_translation_qs.first() if self.translation.language == self.parent.language: msg = ('Both projects can not have the same language ({lang}).') raise forms.ValidationError( - _(msg).format(lang=self.parent.get_language_display()) + _(msg).format(lang=self.parent.get_language_display()), ) exists_translation = ( self.parent.translations.filter(language=self.translation.language).exists() # yapf: disabled @@ -606,7 +616,7 @@ class TranslationBaseForm(forms.Form): if exists_translation: msg = ('This project already has a translation for {lang}.') raise forms.ValidationError( - _(msg).format(lang=self.translation.get_language_display()) + _(msg).format(lang=self.translation.get_language_display()), ) is_parent = self.translation.translations.exists() if is_parent: @@ -692,7 +702,7 @@ class DomainBaseForm(forms.ModelForm): _id = self.initial.get('id') if canonical and Domain.objects.filter(project=self.project, canonical=True).exclude(pk=_id).exists(): # yapf: disabled # noqa raise forms.ValidationError( - _('Only 1 Domain can be canonical at a time.') + _('Only 1 Domain can be canonical at a time.'), ) return canonical @@ -797,7 +807,7 @@ class EnvironmentVariableForm(forms.ModelForm): elif self.project.environmentvariable_set.filter(name=name).exists(): raise forms.ValidationError( _( - 'There is already a variable with this name for this project' + 'There is already a variable with this name for this project', ), ) elif ' ' in name: diff --git a/readthedocs/projects/migrations/0007_migrate_canonical_data.py b/readthedocs/projects/migrations/0007_migrate_canonical_data.py index f1f8fe057..633f975fc 100644 --- a/readthedocs/projects/migrations/0007_migrate_canonical_data.py +++ b/readthedocs/projects/migrations/0007_migrate_canonical_data.py @@ -16,7 +16,7 @@ def migrate_canonical(apps, schema_editor): except Exception as e: print(e) print('Failed adding {url} to {project}'.format( - url=project.canonical_url, project=project.name + url=project.canonical_url, project=project.name, )) @@ -27,5 +27,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(migrate_canonical) + migrations.RunPython(migrate_canonical), ] diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py index bcdee33d1..49d3dc07b 100644 --- a/readthedocs/projects/migrations/0010_migrate_domain_data.py +++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py @@ -12,7 +12,8 @@ def migrate_url(apps, schema_editor): for domain in Domain.objects.all(): if domain.project.superprojects.count() or domain.project.main_language_project: print('{project} is a subproject or translation. Deleting domain.'.format( - project=domain.project.slug)) + project=domain.project.slug, + )) domain.delete() continue parsed = urlparse(domain.url) diff --git a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py index 0b14b4bd7..91f27b6d0 100644 --- a/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py +++ b/readthedocs/projects/migrations/0021_add-webhook-deprecation-feature.py @@ -28,5 +28,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(forward_add_feature, reverse_add_feature) + migrations.RunPython(forward_add_feature, reverse_add_feature), ] diff --git a/readthedocs/projects/migrations/0023_migrate-alias-slug.py b/readthedocs/projects/migrations/0023_migrate-alias-slug.py index c38907700..531c3dc33 100644 --- a/readthedocs/projects/migrations/0023_migrate-alias-slug.py +++ b/readthedocs/projects/migrations/0023_migrate-alias-slug.py @@ -27,5 +27,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(migrate_data, reverse) + migrations.RunPython(migrate_data, reverse), ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index ae3cbdc0e..925b9d477 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -88,44 +88,46 @@ class Project(models.Model): # Generally from conf.py users = models.ManyToManyField( - User, verbose_name=_('User'), related_name='projects' + User, verbose_name=_('User'), related_name='projects', ) # A DNS label can contain up to 63 characters. name = models.CharField(_('Name'), max_length=63) slug = models.SlugField(_('Slug'), max_length=63, unique=True) description = models.TextField( _('Description'), blank=True, - help_text=_('The reStructuredText ' - 'description of the project') + help_text=_( + 'The reStructuredText ' + 'description of the project', + ), ) repo = models.CharField( _('Repository URL'), max_length=255, validators=[validate_repository_url], - help_text=_('Hosted documentation repository URL') + help_text=_('Hosted documentation repository URL'), ) repo_type = models.CharField( _('Repository type'), max_length=10, choices=constants.REPO_CHOICES, - default='git' + default='git', ) project_url = models.URLField( _('Project homepage'), blank=True, - help_text=_('The project\'s homepage') + help_text=_('The project\'s homepage'), ) canonical_url = models.URLField( _('Canonical URL'), blank=True, - help_text=_('URL that documentation is expected to serve from') + help_text=_('URL that documentation is expected to serve from'), ) single_version = models.BooleanField( _('Single version'), default=False, help_text=_( 'A single version site has no translations and only your ' '"latest" version, served at the root of the domain. Use ' 'this with caution, only turn it on if you will never ' - 'have multiple versions of your docs.' - ) + 'have multiple versions of your docs.', + ), ) default_version = models.CharField( _('Default version'), max_length=255, default=LATEST, - help_text=_('The version of your project that / redirects to') + help_text=_('The version of your project that / redirects to'), ) # In default_branch, None means the backend should choose the # appropriate branch. Eg 'master' for git @@ -134,8 +136,8 @@ class Project(models.Model): blank=True, help_text=_( 'What branch "latest" points to. Leave empty ' 'to use the default value for your VCS (eg. ' - 'trunk or master).' - ) + 'trunk or master).', + ), ) requirements_file = models.CharField( _('Requirements file'), max_length=255, default=None, null=True, @@ -143,16 +145,16 @@ class Project(models.Model): 'A ' 'pip requirements file needed to build your documentation. ' - 'Path from the root of your project.' - ) + 'Path from the root of your project.', + ), ) documentation_type = models.CharField( _('Documentation type'), max_length=20, choices=constants.DOCUMENTATION_CHOICES, default='sphinx', help_text=_( 'Type of documentation you are building. More info.' - ) + 'DirectoryHTMLBuilder">More info.', + ), ) # Project features @@ -161,18 +163,18 @@ class Project(models.Model): _('Analytics code'), max_length=50, null=True, blank=True, help_text=_( 'Google Analytics Tracking ID ' '(ex. UA-22345342-1). ' - 'This may slow down your page loads.' - ) + 'This may slow down your page loads.', + ), ) container_image = models.CharField( - _('Alternative container image'), max_length=64, null=True, blank=True + _('Alternative container image'), max_length=64, null=True, blank=True, ) container_mem_limit = models.CharField( _('Container memory limit'), max_length=10, null=True, blank=True, help_text=_( 'Memory limit in Docker format ' - '-- example: 512m or 1g' - ) + '-- example: 512m or 1g', + ), ) container_time_limit = models.IntegerField( _('Container time limit in seconds'), @@ -180,11 +182,11 @@ class Project(models.Model): blank=True, ) build_queue = models.CharField( - _('Alternate build queue id'), max_length=32, null=True, blank=True + _('Alternate build queue id'), max_length=32, null=True, blank=True, ) allow_promos = models.BooleanField( _('Allow paid advertising'), default=True, - help_text=_('If unchecked, users will still see community ads.') + help_text=_('If unchecked, users will still see community ads.'), ) ad_free = models.BooleanField( _('Ad-free'), @@ -193,34 +195,36 @@ class Project(models.Model): ) show_version_warning = models.BooleanField( _('Show version warning'), default=False, - help_text=_('Show warning banner in non-stable nor latest versions.') + help_text=_('Show warning banner in non-stable nor latest versions.'), ) # Sphinx specific build options. enable_epub_build = models.BooleanField( _('Enable EPUB build'), default=True, help_text=_( - 'Create a EPUB version of your documentation with each build.' - ) + 'Create a EPUB version of your documentation with each build.', + ), ) enable_pdf_build = models.BooleanField( _('Enable PDF build'), default=True, help_text=_( - 'Create a PDF version of your documentation with each build.' - ) + 'Create a PDF version of your documentation with each build.', + ), ) # Other model data. path = models.CharField( _('Path'), max_length=255, editable=False, - help_text=_('The directory where ' - 'conf.py lives') + help_text=_( + 'The directory where ' + 'conf.py lives', + ), ) conf_py_file = models.CharField( _('Python configuration file'), max_length=255, default='', blank=True, help_text=_( 'Path from project root to conf.py file ' '(ex. docs/conf.py). ' - 'Leave blank if you want us to find it for you.' - ) + 'Leave blank if you want us to find it for you.', + ), ) featured = models.BooleanField(_('Featured'), default=False) @@ -228,8 +232,8 @@ class Project(models.Model): install_project = models.BooleanField( _('Install Project'), help_text=_( 'Install your project inside a virtualenv using setup.py ' - 'install' - ), default=False + 'install', + ), default=False, ) # This model attribute holds the python interpreter used to create the @@ -238,38 +242,42 @@ class Project(models.Model): _('Python Interpreter'), max_length=20, choices=constants.PYTHON_CHOICES, default='python', help_text=_( 'The Python interpreter used to create the virtual ' - 'environment.' - ) + 'environment.', + ), ) use_system_packages = models.BooleanField( _('Use system packages'), help_text=_( 'Give the virtual environment access to the global ' - 'site-packages dir.' - ), default=False + 'site-packages dir.', + ), default=False, ) privacy_level = models.CharField( _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, - default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', - 'public'), help_text=_( + default=getattr( + settings, 'DEFAULT_PRIVACY_LEVEL', + 'public', + ), help_text=_( 'Level of privacy that you want on the repository. ' - 'Protected means public but not in listings.' - ) + 'Protected means public but not in listings.', + ), ) version_privacy_level = models.CharField( _('Version Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, - default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', - 'public'), help_text=_( + default=getattr( + settings, 'DEFAULT_PRIVACY_LEVEL', + 'public', + ), help_text=_( 'Default level of privacy you want on built ' - 'versions of documentation.' - ) + 'versions of documentation.', + ), ) # Subprojects related_projects = models.ManyToManyField( 'self', verbose_name=_('Related projects'), blank=True, - symmetrical=False, through=ProjectRelationship + symmetrical=False, through=ProjectRelationship, ) # Language bits @@ -277,27 +285,27 @@ class Project(models.Model): _('Language'), max_length=20, default='en', help_text=_( 'The language the project ' 'documentation is rendered in. ' - "Note: this affects your project's URL." - ), choices=constants.LANGUAGES + "Note: this affects your project's URL.", + ), choices=constants.LANGUAGES, ) programming_language = models.CharField( _('Programming Language'), max_length=20, default='words', help_text=_( - 'The primary programming language the project is written in.' - ), choices=constants.PROGRAMMING_LANGUAGES, blank=True + 'The primary programming language the project is written in.', + ), choices=constants.PROGRAMMING_LANGUAGES, blank=True, ) # A subproject pointed at its main language, so it can be tracked main_language_project = models.ForeignKey( 'self', related_name='translations', on_delete=models.SET_NULL, - blank=True, null=True + blank=True, null=True, ) has_valid_webhook = models.BooleanField( default=False, - help_text=_('This project has been built with a webhook') + help_text=_('This project has been built with a webhook'), ) has_valid_clone = models.BooleanField( - default=False, help_text=_('This project has been successfully cloned') + default=False, help_text=_('This project has been successfully cloned'), ) tags = TaggableManager(blank=True) @@ -386,14 +394,14 @@ class Project(models.Model): """ return resolve( project=self, version_slug=version_slug, language=lang_slug, - private=private + private=private, ) def get_builds_url(self): return reverse( 'builds_project_list', kwargs={ 'project_slug': self.slug, - } + }, ) def get_canonical_url(self): @@ -412,7 +420,8 @@ class Project(models.Model): for proj in ( api.project(self.pk) .subprojects() - .get()['subprojects'])] + .get()['subprojects'] + )] return [(proj.child.slug, proj.child.get_docs_url()) for proj in self.subprojects.all()] @@ -429,23 +438,28 @@ class Project(models.Model): """ if getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public') == 'public' or settings.DEBUG: path = os.path.join( - settings.MEDIA_ROOT, type_, self.slug, version_slug) + settings.MEDIA_ROOT, type_, self.slug, version_slug, + ) else: path = os.path.join( - settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug) + settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug, + ) if include_file: path = os.path.join( - path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip'))) + path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')), + ) return path def get_production_media_url(self, type_, version_slug, full_path=True): """Get the URL for downloading a specific media file.""" try: - path = reverse('project_download_media', kwargs={ - 'project_slug': self.slug, - 'type_': type_, - 'version_slug': version_slug, - }) + path = reverse( + 'project_download_media', kwargs={ + 'project_slug': self.slug, + 'type_': type_, + 'version_slug': version_slug, + }, + ) except NoReverseMatch: return '' if full_path: @@ -459,13 +473,13 @@ class Project(models.Model): def get_downloads(self): downloads = {} downloads['htmlzip'] = self.get_production_media_url( - 'htmlzip', self.get_default_version() + 'htmlzip', self.get_default_version(), ) downloads['epub'] = self.get_production_media_url( - 'epub', self.get_default_version() + 'epub', self.get_default_version(), ) downloads['pdf'] = self.get_production_media_url( - 'pdf', self.get_default_version() + 'pdf', self.get_default_version(), ) return downloads @@ -591,7 +605,7 @@ class Project(models.Model): # the `doc` word in the path, we raise an error informing this to the user if len(files) > 1: raise ProjectConfigurationError( - ProjectConfigurationError.MULTIPLE_CONF_FILES + ProjectConfigurationError.MULTIPLE_CONF_FILES, ) raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) @@ -621,17 +635,20 @@ class Project(models.Model): if not self.enable_pdf_build: return False return os.path.exists(self.get_production_media_path( - type_='pdf', version_slug=version_slug)) + type_='pdf', version_slug=version_slug, + )) def has_epub(self, version_slug=LATEST): if not self.enable_epub_build: return False return os.path.exists(self.get_production_media_path( - type_='epub', version_slug=version_slug)) + type_='epub', version_slug=version_slug, + )) def has_htmlzip(self, version_slug=LATEST): return os.path.exists(self.get_production_media_path( - type_='htmlzip', version_slug=version_slug)) + type_='htmlzip', version_slug=version_slug, + )) @property def sponsored(self): @@ -780,8 +797,8 @@ class Project(models.Model): if identifier_updated and current_stable.active and current_stable.machine: log.info( 'Update stable version: {project}:{version}'.format( - project=self.slug, version=new_stable.identifier - ) + project=self.slug, version=new_stable.identifier, + ), ) current_stable.identifier = new_stable.identifier current_stable.save() @@ -789,10 +806,10 @@ class Project(models.Model): else: log.info( 'Creating new stable version: {project}:{version}' - .format(project=self.slug, version=new_stable.identifier) + .format(project=self.slug, version=new_stable.identifier), ) current_stable = self.versions.create_stable( - type=new_stable.type, identifier=new_stable.identifier + type=new_stable.type, identifier=new_stable.identifier, ) return new_stable @@ -816,7 +833,7 @@ class Project(models.Model): return self.default_version # check if the default_version exists version_qs = self.versions.filter( - slug=self.default_version, active=True + slug=self.default_version, active=True, ) if version_qs.exists(): return self.default_version @@ -915,8 +932,10 @@ class APIProject(Project): ad_free = (not kwargs.pop('show_advertising', True)) # These fields only exist on the API return, not on the model, so we'll # remove them to avoid throwing exceptions due to unexpected fields - for key in ['users', 'resource_uri', 'absolute_url', 'downloads', - 'main_language_project', 'related_projects']: + for key in [ + 'users', 'resource_uri', 'absolute_url', 'downloads', + 'main_language_project', 'related_projects', + ]: try: del kwargs[key] except KeyError: @@ -954,11 +973,11 @@ class ImportedFile(models.Model): """ project = models.ForeignKey( - 'Project', verbose_name=_('Project'), related_name='imported_files' + 'Project', verbose_name=_('Project'), related_name='imported_files', ) version = models.ForeignKey( 'builds.Version', verbose_name=_('Version'), - related_name='imported_files', null=True + related_name='imported_files', null=True, ) name = models.CharField(_('Name'), max_length=255) slug = models.SlugField(_('Slug')) @@ -970,7 +989,7 @@ class ImportedFile(models.Model): def get_absolute_url(self): return resolve( project=self.project, version_slug=self.version.slug, - filename=self.path + filename=self.path, ) def __str__(self): @@ -996,7 +1015,7 @@ class EmailHook(Notification): @python_2_unicode_compatible class WebHook(Notification): url = models.URLField( - max_length=600, blank=True, help_text=_('URL to send the webhook to') + max_length=600, blank=True, help_text=_('URL to send the webhook to'), ) def __str__(self): @@ -1011,23 +1030,23 @@ class Domain(models.Model): project = models.ForeignKey(Project, related_name='domains') domain = models.CharField( _('Domain'), unique=True, max_length=255, - validators=[validate_domain_name] + validators=[validate_domain_name], ) machine = models.BooleanField( - default=False, help_text=_('This Domain was auto-created') + default=False, help_text=_('This Domain was auto-created'), ) cname = models.BooleanField( - default=False, help_text=_('This Domain is a CNAME for the project') + default=False, help_text=_('This Domain is a CNAME for the project'), ) canonical = models.BooleanField( default=False, help_text=_( 'This Domain is the primary one where the documentation is ' - 'served from' - ) + 'served from', + ), ) https = models.BooleanField( _('Use HTTPS'), default=False, - help_text=_('Always use HTTPS for this domain') + help_text=_('Always use HTTPS for this domain'), ) count = models.IntegerField( default=0, @@ -1041,7 +1060,7 @@ class Domain(models.Model): def __str__(self): return '{domain} pointed at {project}'.format( - domain=self.domain, project=self.project.name + domain=self.domain, project=self.project.name, ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -1104,15 +1123,27 @@ class Feature(models.Model): (ALLOW_DEPRECATED_WEBHOOKS, _('Allow deprecated webhook views')), (PIP_ALWAYS_UPGRADE, _('Always run pip install --upgrade')), (SKIP_SUBMODULES, _('Skip git submodule checkout')), - (DONT_OVERWRITE_SPHINX_CONTEXT, _( - 'Do not overwrite context vars in conf.py with Read the Docs context')), - (ALLOW_V2_CONFIG_FILE, _( - 'Allow to use the v2 of the configuration file')), + ( + DONT_OVERWRITE_SPHINX_CONTEXT, _( + 'Do not overwrite context vars in conf.py with Read the Docs context', + ), + ), + ( + ALLOW_V2_CONFIG_FILE, _( + 'Allow to use the v2 of the configuration file', + ), + ), (MKDOCS_THEME_RTD, _('Use Read the Docs theme for MkDocs as default theme')), - (DONT_SHALLOW_CLONE, _( - 'Do not shallow clone when cloning git repos')), - (USE_TESTING_BUILD_IMAGE, _( - 'Use Docker image labelled as `testing` to build the docs')), + ( + DONT_SHALLOW_CLONE, _( + 'Do not shallow clone when cloning git repos', + ), + ), + ( + USE_TESTING_BUILD_IMAGE, _( + 'Use Docker image labelled as `testing` to build the docs', + ), + ), ) projects = models.ManyToManyField( diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 95ec9dbbe..640a08c9c 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -142,7 +142,7 @@ class SyncRepositoryMixin: project=self.project.slug, version=self.version.slug, msg=msg, - ) + ), ) version_repo = self.get_vcs_repo() version_repo.update() @@ -161,16 +161,20 @@ class SyncRepositoryMixin: version_post_data = {'repo': version_repo.repo_url} if version_repo.supports_tags: - version_post_data['tags'] = [{ - 'identifier': v.identifier, - 'verbose_name': v.verbose_name, - } for v in version_repo.tags] + version_post_data['tags'] = [ + { + 'identifier': v.identifier, + 'verbose_name': v.verbose_name, + } for v in version_repo.tags + ] if version_repo.supports_branches: - version_post_data['branches'] = [{ - 'identifier': v.identifier, - 'verbose_name': v.verbose_name, - } for v in version_repo.branches] + version_post_data['branches'] = [ + { + 'identifier': v.identifier, + 'verbose_name': v.verbose_name, + } for v in version_repo.branches + ] self.validate_duplicate_reserved_versions(version_post_data) @@ -471,7 +475,7 @@ class UpdateDocsTaskStep(SyncRepositoryMixin): project=self.project.slug, version=self.version.slug, msg=msg, - ) + ), ) # Send notification to users only if the build didn't fail because @@ -532,7 +536,7 @@ class UpdateDocsTaskStep(SyncRepositoryMixin): project=self.project.slug, version=self.version.slug, msg='Using conda', - ) + ), ) python_env_cls = Conda self.python_env = python_env_cls( @@ -613,7 +617,7 @@ class UpdateDocsTaskStep(SyncRepositoryMixin): project=self.project.slug, version=self.version.slug, msg='Updating docs from VCS', - ) + ), ) try: self.sync_repo() @@ -1096,7 +1100,7 @@ def remove_orphan_symlinks(): for symlink in [PublicSymlink, PrivateSymlink]: for domain_path in [symlink.PROJECT_CNAME_ROOT, symlink.CNAME_ROOT]: valid_cnames = set( - Domain.objects.all().values_list('domain', flat=True) + Domain.objects.all().values_list('domain', flat=True), ) orphan_cnames = set(os.listdir(domain_path)) - valid_cnames for cname in orphan_cnames: @@ -1225,8 +1229,10 @@ def send_notifications(version_pk, build_pk): for hook in version.project.webhook_notifications.all(): webhook_notification(version, build, hook.url) - for email in version.project.emailhook_notifications.all().values_list('email', - flat=True): + for email in version.project.emailhook_notifications.all().values_list( + 'email', + flat=True, + ): email_notification(version, build, email) @@ -1270,8 +1276,9 @@ def email_notification(version, build, email): } if build.commit: - title = _('Failed: {project[name]} ({commit})' - ).format(commit=build.commit[:8], **context) + title = _( + 'Failed: {project[name]} ({commit})', + ).format(commit=build.commit[:8], **context) else: title = _('Failed: {project[name]} ({version[verbose_name]})').format( **context diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index eebec75c1..c1dd9b20c 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -33,99 +33,99 @@ urlpatterns = [ url(r'^$', ProjectDashboard.as_view(), name='projects_dashboard'), url( r'^import/$', ImportView.as_view(wizard_class=ImportWizardView), - {'wizard': ImportWizardView}, name='projects_import' + {'wizard': ImportWizardView}, name='projects_import', ), url( r'^import/manual/$', ImportWizardView.as_view(), - name='projects_import_manual' + name='projects_import_manual', ), url( r'^import/manual/demo/$', ImportDemoView.as_view(), - name='projects_import_demo' + name='projects_import_demo', ), url( r'^(?P[-\w]+)/$', private.project_manage, - name='projects_manage' + name='projects_manage', ), url( r'^(?P[-\w]+)/edit/$', ProjectUpdate.as_view(), - name='projects_edit' + name='projects_edit', ), url( r'^(?P[-\w]+)/advanced/$', - ProjectAdvancedUpdate.as_view(), name='projects_advanced' + ProjectAdvancedUpdate.as_view(), name='projects_advanced', ), url( r'^(?P[-\w]+)/version/(?P[^/]+)/delete_html/$', - private.project_version_delete_html, name='project_version_delete_html' + private.project_version_delete_html, name='project_version_delete_html', ), url( r'^(?P[-\w]+)/version/(?P[^/]+)/$', - private.project_version_detail, name='project_version_detail' + private.project_version_detail, name='project_version_detail', ), url( r'^(?P[-\w]+)/versions/$', private.project_versions, - name='projects_versions' + name='projects_versions', ), url( r'^(?P[-\w]+)/delete/$', private.project_delete, - name='projects_delete' + name='projects_delete', ), url( r'^(?P[-\w]+)/users/$', private.project_users, - name='projects_users' + name='projects_users', ), url( r'^(?P[-\w]+)/users/delete/$', - private.project_users_delete, name='projects_users_delete' + private.project_users_delete, name='projects_users_delete', ), url( r'^(?P[-\w]+)/notifications/$', - private.project_notifications, name='projects_notifications' + private.project_notifications, name='projects_notifications', ), url( r'^(?P[-\w]+)/notifications/delete/$', - private.project_notifications_delete, name='projects_notification_delete' + private.project_notifications_delete, name='projects_notification_delete', ), url( r'^(?P[-\w]+)/translations/$', - private.project_translations, name='projects_translations' + private.project_translations, name='projects_translations', ), url( r'^(?P[-\w]+)/translations/delete/(?P[-\w]+)/$', # noqa private.project_translations_delete, - name='projects_translations_delete' + name='projects_translations_delete', ), url( r'^(?P[-\w]+)/redirects/$', private.project_redirects, - name='projects_redirects' + name='projects_redirects', ), url( r'^(?P[-\w]+)/redirects/delete/$', - private.project_redirects_delete, name='projects_redirects_delete' + private.project_redirects_delete, name='projects_redirects_delete', ), url( r'^(?P[-\w]+)/advertising/$', - ProjectAdvertisingUpdate.as_view(), name='projects_advertising' + ProjectAdvertisingUpdate.as_view(), name='projects_advertising', ), ] domain_urls = [ url( r'^(?P[-\w]+)/domains/$', DomainList.as_view(), - name='projects_domains' + name='projects_domains', ), url( r'^(?P[-\w]+)/domains/create/$', DomainCreate.as_view(), - name='projects_domains_create' + name='projects_domains_create', ), url( r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$', - DomainUpdate.as_view(), name='projects_domains_edit' + DomainUpdate.as_view(), name='projects_domains_edit', ), url( r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$', - DomainDelete.as_view(), name='projects_domains_delete' + DomainDelete.as_view(), name='projects_domains_delete', ), ] @@ -135,44 +135,54 @@ integration_urls = [ url( r'^(?P{project_slug})/integrations/$'.format( **pattern_opts - ), IntegrationList.as_view(), name='projects_integrations' + ), IntegrationList.as_view(), name='projects_integrations', ), url( r'^(?P{project_slug})/integrations/sync/$'.format( **pattern_opts ), IntegrationWebhookSync.as_view(), - name='projects_integrations_webhooks_sync' + name='projects_integrations_webhooks_sync', + ), + url( + ( + r'^(?P{project_slug})/integrations/create/$'.format( + **pattern_opts + ) + ), IntegrationCreate.as_view(), name='projects_integrations_create', + ), + url( + ( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/$'.format( + **pattern_opts + ) + ), IntegrationDetail.as_view(), name='projects_integrations_detail', + ), + url( + ( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/' + r'exchange/(?P[-\w]+)/$'.format(**pattern_opts) + ), IntegrationExchangeDetail.as_view(), + name='projects_integrations_exchanges_detail', + ), + url( + ( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/sync/$'.format( + **pattern_opts + ) + ), IntegrationWebhookSync.as_view(), + name='projects_integrations_webhooks_sync', + ), + url( + ( + r'^(?P{project_slug})/' + r'integrations/(?P{integer_pk})/delete/$'.format( + **pattern_opts + ) + ), IntegrationDelete.as_view(), name='projects_integrations_delete', ), - url(( - r'^(?P{project_slug})/integrations/create/$'.format( - **pattern_opts - ) - ), IntegrationCreate.as_view(), name='projects_integrations_create'), - url(( - r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/$'.format( - **pattern_opts - ) - ), IntegrationDetail.as_view(), name='projects_integrations_detail'), - url(( - r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/' - r'exchange/(?P[-\w]+)/$'.format(**pattern_opts) - ), IntegrationExchangeDetail.as_view(), - name='projects_integrations_exchanges_detail'), - url(( - r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/sync/$'.format( - **pattern_opts - ) - ), IntegrationWebhookSync.as_view(), - name='projects_integrations_webhooks_sync'), - url(( - r'^(?P{project_slug})/' - r'integrations/(?P{integer_pk})/delete/$'.format( - **pattern_opts - ) - ), IntegrationDelete.as_view(), name='projects_integrations_delete'), ] urlpatterns += integration_urls @@ -182,28 +192,34 @@ subproject_urls = [ r'^(?P{project_slug})/subprojects/$'.format( **pattern_opts ), private.ProjectRelationshipList.as_view(), - name='projects_subprojects' + name='projects_subprojects', + ), + url( + ( + r'^(?P{project_slug})/subprojects/create/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipCreate.as_view(), + name='projects_subprojects_create', + ), + url( + ( + r'^(?P{project_slug})/' + r'subprojects/(?P{project_slug})/edit/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipUpdate.as_view(), + name='projects_subprojects_update', + ), + url( + ( + r'^(?P{project_slug})/' + r'subprojects/(?P{project_slug})/delete/$'.format( + **pattern_opts + ) + ), private.ProjectRelationshipDelete.as_view(), + name='projects_subprojects_delete', ), - url(( - r'^(?P{project_slug})/subprojects/create/$'.format( - **pattern_opts - ) - ), private.ProjectRelationshipCreate.as_view(), - name='projects_subprojects_create'), - url(( - r'^(?P{project_slug})/' - r'subprojects/(?P{project_slug})/edit/$'.format( - **pattern_opts - ) - ), private.ProjectRelationshipUpdate.as_view(), - name='projects_subprojects_update'), - url(( - r'^(?P{project_slug})/' - r'subprojects/(?P{project_slug})/delete/$'.format( - **pattern_opts - ) - ), private.ProjectRelationshipDelete.as_view(), - name='projects_subprojects_delete'), ] urlpatterns += subproject_urls @@ -211,22 +227,22 @@ urlpatterns += subproject_urls environmentvariable_urls = [ url( r'^(?P[-\w]+)/environmentvariables/$', - EnvironmentVariableList.as_view(), name='projects_environmentvariables' + EnvironmentVariableList.as_view(), name='projects_environmentvariables', ), url( r'^(?P[-\w]+)/environmentvariables/create/$', EnvironmentVariableCreate.as_view(), - name='projects_environmentvariables_create' + name='projects_environmentvariables_create', ), url( r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/$', EnvironmentVariableDetail.as_view(), - name='projects_environmentvariables_detail' + name='projects_environmentvariables_detail', ), url( r'^(?P[-\w]+)/environmentvariables/(?P[-\w]+)/delete/$', EnvironmentVariableDelete.as_view(), - name='projects_environmentvariables_delete' + name='projects_environmentvariables_delete', ), ] diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 09f7592d7..7a730e61f 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -118,9 +118,11 @@ def determine_stable_version(version_list): :rtype: readthedocs.builds.models.Version """ versions = sort_versions(version_list) - versions = [(version_obj, comparable) - for version_obj, comparable in versions - if not comparable.is_prerelease] + versions = [ + (version_obj, comparable) + for version_obj, comparable in versions + if not comparable.is_prerelease + ] if versions: # We take preference for tags over branches. If we don't find any tag, diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index aa85f2d64..41b3acda3 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -220,7 +220,7 @@ def project_delete(request, project_slug): if request.method == 'POST': broadcast( - type='app', task=tasks.remove_dirs, args=[(project.doc_path,)] + type='app', task=tasks.remove_dirs, args=[(project.doc_path,)], ) project.delete() messages.success(request, _('Project deleted')) diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py index 3a15e756c..9256e92e0 100644 --- a/readthedocs/projects/views/public.py +++ b/readthedocs/projects/views/public.py @@ -115,8 +115,10 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView): def project_badge(request, project_slug): """Return a sweet badge for the project.""" style = request.GET.get('style', 'flat') - if style not in ('flat', 'plastic', 'flat-square', 'for-the-badge', - 'social'): + if style not in ( + 'flat', 'plastic', 'flat-square', 'for-the-badge', + 'social', + ): style = 'flat' # Get the local path to the badge files @@ -138,8 +140,10 @@ def project_badge(request, project_slug): ).first() if version: - last_build = version.builds.filter(type='html', - state='finished').order_by('-date').first() + last_build = version.builds.filter( + type='html', + state='finished', + ).order_by('-date').first() if last_build: if last_build.success: file_path = badge_path % 'passing' @@ -154,7 +158,7 @@ def project_badge(request, project_slug): ) except (IOError, OSError): log.exception( - 'Failed to read local filesystem while serving a docs badge' + 'Failed to read local filesystem while serving a docs badge', ) return HttpResponse(status=503) @@ -415,8 +419,9 @@ def project_embed(request, project_slug): slug=project_slug, ) version = project.versions.get(slug=LATEST) - files = version.imported_files.filter(name__endswith='.html' - ).order_by('path') + files = version.imported_files.filter( + name__endswith='.html', + ).order_by('path') return render( request, diff --git a/readthedocs/restapi/views/core_views.py b/readthedocs/restapi/views/core_views.py index 31d1d5f75..578f11232 100644 --- a/readthedocs/restapi/views/core_views.py +++ b/readthedocs/restapi/views/core_views.py @@ -28,8 +28,10 @@ def docurl(request): version = request.GET.get('version', LATEST) doc = request.GET.get('doc', 'index') if project is None: - return Response({'error': 'Need project and doc'}, - status=status.HTTP_400_BAD_REQUEST) + return Response( + {'error': 'Need project and doc'}, + status=status.HTTP_400_BAD_REQUEST, + ) project = get_object_or_404(Project, slug=project) version = get_object_or_404( @@ -39,6 +41,6 @@ def docurl(request): ) return Response({ 'url': make_document_url( - project=project, version=version.slug, page=doc + project=project, version=version.slug, page=doc, ), }) diff --git a/readthedocs/restapi/views/footer_views.py b/readthedocs/restapi/views/footer_views.py index 779371b07..bf66f5f80 100644 --- a/readthedocs/restapi/views/footer_views.py +++ b/readthedocs/restapi/views/footer_views.py @@ -95,8 +95,10 @@ def footer_html(request): main_project = project.main_language_project or project if page_slug and page_slug != 'index': - if (main_project.documentation_type == 'sphinx_htmldir' or - main_project.documentation_type == 'mkdocs'): + if ( + main_project.documentation_type == 'sphinx_htmldir' or + main_project.documentation_type == 'mkdocs' + ): path = page_slug + '/' elif main_project.documentation_type == 'sphinx_singlehtml': path = 'index.html#document-' + page_slug diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py index ffb150363..7f44fb4f1 100644 --- a/readthedocs/restapi/views/integrations.py +++ b/readthedocs/restapi/views/integrations.py @@ -379,8 +379,9 @@ class APIWebhookView(WebhookMixin, APIView): if self.request.user.is_authenticated: try: return ( - Project.objects.for_admin_user(self.request.user - ).get(**kwargs) + Project.objects.for_admin_user( + self.request.user, + ).get(**kwargs) ) except Project.DoesNotExist: pass diff --git a/readthedocs/restapi/views/model_views.py b/readthedocs/restapi/views/model_views.py index 692f5ac06..f0586e2fa 100644 --- a/readthedocs/restapi/views/model_views.py +++ b/readthedocs/restapi/views/model_views.py @@ -80,8 +80,10 @@ class UserSelectViewSet(viewsets.ModelViewSet): def get_serializer_class(self): try: - if (self.request.user.is_staff and - self.admin_serializer_class is not None): + if ( + self.request.user.is_staff and + self.admin_serializer_class is not None + ): return self.admin_serializer_class except AttributeError: pass @@ -136,7 +138,7 @@ class ProjectViewSet(UserSelectViewSet): }) @decorators.action( - detail=True, permission_classes=[permissions.IsAdminUser] + detail=True, permission_classes=[permissions.IsAdminUser], ) def token(self, request, **kwargs): project = get_object_or_404( @@ -225,8 +227,10 @@ class ProjectViewSet(UserSelectViewSet): # Marking the tag that is considered the new stable version as # active and building it if it was just added. - if (activate_new_stable and - promoted_version.slug in added_versions): + if ( + activate_new_stable and + promoted_version.slug in added_versions + ): promoted_version.active = True promoted_version.save() trigger_build(project=project, version=promoted_version) diff --git a/readthedocs/restapi/views/search_views.py b/readthedocs/restapi/views/search_views.py index da0dab870..aa2acc1fd 100644 --- a/readthedocs/restapi/views/search_views.py +++ b/readthedocs/restapi/views/search_views.py @@ -103,8 +103,10 @@ def search(request): def project_search(request): query = request.GET.get('q', None) if query is None: - return Response({'error': 'Need project and q'}, - status=status.HTTP_400_BAD_REQUEST) + return Response( + {'error': 'Need project and q'}, + status=status.HTTP_400_BAD_REQUEST, + ) log.debug('(API Project Search) %s', (query)) results = search_project(request=request, query=query) return Response({'results': results}) diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py index 123e8b7f7..e7d8f8409 100644 --- a/readthedocs/rtd_tests/base.py +++ b/readthedocs/rtd_tests/base.py @@ -145,7 +145,7 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase): response.render() self.assertContains( response, - 'name="{}-current_step"'.format(self.wizard_class_slug) + 'name="{}-current_step"'.format(self.wizard_class_slug), ) # We use camelCase on purpose here to conform with unittest's naming diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index 972ec96be..c4c2ded07 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -10,7 +10,7 @@ templates_path = ['templates', '_templates', '.templates'] source_suffix = ['.rst', '.md'] source_parsers = { '.md': CommonMarkParser, - } +} master_doc = 'index' project = 'Pip' copyright = str(datetime.now().year) @@ -22,6 +22,8 @@ htmlhelp_basename = 'pip' html_theme = 'sphinx_rtd_theme' file_insertion_enabled = False latex_documents = [ - ('index', 'pip.tex', 'Pip Documentation', - '', 'manual'), + ( + 'index', 'pip.tex', 'Pip Documentation', + '', 'manual', + ), ] diff --git a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py index 68b327ca8..e6c4dad0f 100644 --- a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py +++ b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py @@ -180,8 +180,10 @@ htmlhelp_basename = 'sampledoc' # Grouping the document tree into LaTeX files. List of tuples (source start # file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'sample.tex', 'sample Documentation', - 'Dan', 'manual'), + ( + 'index', 'sample.tex', 'sample Documentation', + 'Dan', 'manual', + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,6 +215,8 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'sample', 'sample Documentation', - ['Dan'], 1) + ( + 'index', 'sample', 'sample Documentation', + ['Dan'], 1, + ), ] diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py index c848fedd6..6928d4cec 100644 --- a/readthedocs/rtd_tests/mocks/environment.py +++ b/readthedocs/rtd_tests/mocks/environment.py @@ -14,43 +14,61 @@ class EnvironmentMockGroup: 'api': mock.patch('slumber.Resource'), 'api_v2.command': mock.patch( 'readthedocs.doc_builder.environments.api_v2.command', - mock.Mock(**{'get.return_value': {}})), + mock.Mock(**{'get.return_value': {}}), + ), 'api_v2.build': mock.patch( 'readthedocs.doc_builder.environments.api_v2.build', - mock.Mock(**{'get.return_value': {}})), + mock.Mock(**{'get.return_value': {}}), + ), 'api_versions': mock.patch( - 'readthedocs.projects.models.Project.api_versions'), + 'readthedocs.projects.models.Project.api_versions', + ), 'non_blocking_lock': mock.patch( - 'readthedocs.vcs_support.utils.NonBlockingLock.__enter__'), + 'readthedocs.vcs_support.utils.NonBlockingLock.__enter__', + ), 'append_conf': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf'), + 'readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf', + ), 'move': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.BaseSphinx.move'), + 'readthedocs.doc_builder.backends.sphinx.BaseSphinx.move', + ), 'conf_dir': mock.patch( - 'readthedocs.projects.models.Project.conf_dir'), + 'readthedocs.projects.models.Project.conf_dir', + ), 'html_build': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.HtmlBuilder.build'), + 'readthedocs.doc_builder.backends.sphinx.HtmlBuilder.build', + ), 'html_move': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.HtmlBuilder.move'), + 'readthedocs.doc_builder.backends.sphinx.HtmlBuilder.move', + ), 'localmedia_build': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.LocalMediaBuilder.build'), + 'readthedocs.doc_builder.backends.sphinx.LocalMediaBuilder.build', + ), 'localmedia_move': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.LocalMediaBuilder.move'), + 'readthedocs.doc_builder.backends.sphinx.LocalMediaBuilder.move', + ), 'pdf_build': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.PdfBuilder.build'), + 'readthedocs.doc_builder.backends.sphinx.PdfBuilder.build', + ), 'pdf_move': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.PdfBuilder.move'), + 'readthedocs.doc_builder.backends.sphinx.PdfBuilder.move', + ), 'epub_build': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.EpubBuilder.build'), + 'readthedocs.doc_builder.backends.sphinx.EpubBuilder.build', + ), 'epub_move': mock.patch( - 'readthedocs.doc_builder.backends.sphinx.EpubBuilder.move'), + 'readthedocs.doc_builder.backends.sphinx.EpubBuilder.move', + ), 'move_mkdocs': mock.patch( - 'readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.move'), + 'readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.move', + ), 'append_conf_mkdocs': mock.patch( - 'readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.append_conf'), + 'readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.append_conf', + ), 'html_build_mkdocs': mock.patch( - 'readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.build'), + 'readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.build', + ), 'glob': mock.patch('readthedocs.doc_builder.backends.sphinx.glob'), 'docker': mock.patch('readthedocs.doc_builder.environments.APIClient'), diff --git a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py index e1a9267ad..dd25f4a13 100644 --- a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py +++ b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py @@ -35,7 +35,7 @@ class ProjectAdminActionsTest(TestCase): } resp = self.client.post( urls.reverse('admin:projects_project_changelist'), - action_data + action_data, ) self.assertTrue(self.project.users.filter(profile__banned=True).exists()) self.assertFalse(self.project.users.filter(profile__banned=False).exists()) @@ -53,7 +53,7 @@ class ProjectAdminActionsTest(TestCase): } resp = self.client.post( urls.reverse('admin:projects_project_changelist'), - action_data + action_data, ) self.assertFalse(self.project.users.filter(profile__banned=True).exists()) self.assertEqual(self.project.users.filter(profile__banned=False).count(), 2) @@ -70,11 +70,11 @@ class ProjectAdminActionsTest(TestCase): } resp = self.client.post( urls.reverse('admin:projects_project_changelist'), - action_data + action_data, ) self.assertFalse(Project.objects.filter(pk=self.project.pk).exists()) broadcast.assert_has_calls([ mock.call( - type='app', task=remove_dirs, args=[(self.project.doc_path,)] + type='app', task=remove_dirs, args=[(self.project.doc_path,)], ), ]) diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index d7c4ab064..f5dcb1cae 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -414,13 +414,13 @@ class APIBuildTests(TestCase): BuildCommandResult, build=build, command='python setup.py install', - output='Installing dependencies...' + output='Installing dependencies...', ) get( BuildCommandResult, build=build, command='git checkout master', - output='Switched to branch "master"' + output='Switched to branch "master"', ) client = APIClient() @@ -440,11 +440,11 @@ class APIBuildTests(TestCase): self.assertIn('[rtd-command-info]', resp.content.decode()) self.assertIn( 'python setup.py install\nInstalling dependencies...', - resp.content.decode() + resp.content.decode(), ) self.assertIn( 'git checkout master\nSwitched to branch "master"', - resp.content.decode() + resp.content.decode(), ) def test_get_raw_log_building(self): @@ -464,7 +464,7 @@ class APIBuildTests(TestCase): BuildCommandResult, build=build, command='git checkout master', - output='Switched to branch "master"' + output='Switched to branch "master"', ) client = APIClient() @@ -484,17 +484,17 @@ class APIBuildTests(TestCase): self.assertIn('[rtd-command-info]', resp.content.decode()) self.assertIn( 'python setup.py install\nInstalling dependencies...', - resp.content.decode() + resp.content.decode(), ) self.assertIn( 'git checkout master\nSwitched to branch "master"', - resp.content.decode() + resp.content.decode(), ) def test_get_raw_log_failure(self): build = get( Build, project_id=1, version_id=1, - builder='foo', success=False, exit_code=1 + builder='foo', success=False, exit_code=1, ) get( BuildCommandResult, @@ -507,7 +507,7 @@ class APIBuildTests(TestCase): BuildCommandResult, build=build, command='git checkout master', - output='Switched to branch "master"' + output='Switched to branch "master"', ) client = APIClient() @@ -527,11 +527,11 @@ class APIBuildTests(TestCase): self.assertIn('[rtd-command-info]', resp.content.decode()) self.assertIn( 'python setup.py install\nInstalling dependencies...', - resp.content.decode() + resp.content.decode(), ) self.assertIn( 'git checkout master\nSwitched to branch "master"', - resp.content.decode() + resp.content.decode(), ) def test_get_invalid_raw_log(self): @@ -814,11 +814,11 @@ class IntegrationsTests(TestCase): self.project = get(Project) self.version = get( Version, slug='master', verbose_name='master', - active=True, project=self.project + active=True, project=self.project, ) self.version_tag = get( Version, slug='v1.0', verbose_name='v1.0', - active=True, project=self.project + active=True, project=self.project, ) self.github_payload = { 'ref': 'master', @@ -870,7 +870,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=self.version, project=self.project)]) + [mock.call(force=True, version=self.version, project=self.project)], + ) client.post( '/api/v2/webhook/github/{}/'.format(self.project.slug), @@ -878,7 +879,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=mock.ANY, project=self.project)]) + [mock.call(force=True, version=mock.ANY, project=self.project)], + ) client.post( '/api/v2/webhook/github/{}/'.format(self.project.slug), @@ -886,7 +888,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=self.version, project=self.project)]) + [mock.call(force=True, version=self.version, project=self.project)], + ) def test_github_webhook_for_tags(self, trigger_build): """GitHub webhook API.""" @@ -898,7 +901,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=self.version_tag, project=self.project)]) + [mock.call(force=True, version=self.version_tag, project=self.project)], + ) client.post( '/api/v2/webhook/github/{}/'.format(self.project.slug), @@ -906,7 +910,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=mock.ANY, project=self.project)]) + [mock.call(force=True, version=mock.ANY, project=self.project)], + ) client.post( '/api/v2/webhook/github/{}/'.format(self.project.slug), @@ -914,7 +919,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=self.version_tag, project=self.project)]) + [mock.call(force=True, version=self.version_tag, project=self.project)], + ) @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_github_create_event(self, sync_repository_task, trigger_build): @@ -985,7 +991,7 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_called_with( - force=True, version=mock.ANY, project=self.project + force=True, version=mock.ANY, project=self.project, ) trigger_build.reset_mock() @@ -1011,7 +1017,7 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_called_with( - force=True, version=self.version_tag, project=self.project + force=True, version=self.version_tag, project=self.project, ) trigger_build.reset_mock() @@ -1024,7 +1030,7 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_called_with( - force=True, version=self.version_tag, project=self.project + force=True, version=self.version_tag, project=self.project, ) trigger_build.reset_mock() @@ -1040,7 +1046,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_gitlab_push_hook_creation( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.gitlab_payload.update( before=GITLAB_NULL_HASH, @@ -1061,7 +1068,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_gitlab_push_hook_deletion( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.gitlab_payload.update( before='95790bf891e76fee5e1747ab589903a6a1f80f22', @@ -1082,7 +1090,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_gitlab_tag_push_hook_creation( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.gitlab_payload.update( object_kind=GITLAB_TAG_PUSH, @@ -1104,7 +1113,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_gitlab_tag_push_hook_deletion( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.gitlab_payload.update( object_kind=GITLAB_TAG_PUSH, @@ -1144,7 +1154,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=mock.ANY, project=self.project)]) + [mock.call(force=True, version=mock.ANY, project=self.project)], + ) client.post( '/api/v2/webhook/bitbucket/{}/'.format(self.project.slug), { @@ -1160,7 +1171,8 @@ class IntegrationsTests(TestCase): format='json', ) trigger_build.assert_has_calls( - [mock.call(force=True, version=mock.ANY, project=self.project)]) + [mock.call(force=True, version=mock.ANY, project=self.project)], + ) trigger_build_call_count = trigger_build.call_count client.post( @@ -1180,7 +1192,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_bitbucket_push_hook_creation( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.bitbucket_payload['push']['changes'][0]['old'] = None resp = client.post( @@ -1198,7 +1211,8 @@ class IntegrationsTests(TestCase): @mock.patch('readthedocs.core.views.hooks.sync_repository_task') def test_bitbucket_push_hook_deletion( - self, sync_repository_task, trigger_build): + self, sync_repository_task, trigger_build, + ): client = APIClient() self.bitbucket_payload['push']['changes'][0]['new'] = None resp = client.post( @@ -1219,7 +1233,8 @@ class IntegrationsTests(TestCase): client = APIClient() resp = client.post( '/api/v2/webhook/bitbucket/{}/'.format(self.project.slug), - {'foo': 'bar'}, format='json', HTTP_X_EVENT_KEY='pull_request') + {'foo': 'bar'}, format='json', HTTP_X_EVENT_KEY='pull_request', + ) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data['detail'], 'Unhandled webhook event') @@ -1283,7 +1298,8 @@ class IntegrationsTests(TestCase): user = get(User) client.force_authenticate(user=user) integration = Integration.objects.create( - project=self.project, integration_type=Integration.API_WEBHOOK) + project=self.project, integration_type=Integration.API_WEBHOOK, + ) self.assertIsNotNone(integration.token) resp = client.post( '/api/v2/webhook/{}/{}/'.format( @@ -1484,7 +1500,7 @@ class APIVersionTests(TestCase): }) url = '{base_url}?{querystring}'.format( base_url=reverse('version-list'), - querystring=data.urlencode() + querystring=data.urlencode(), ) resp = self.client.get(url, content_type='application/json') @@ -1498,7 +1514,7 @@ class APIVersionTests(TestCase): }) url = '{base_url}?{querystring}'.format( base_url=reverse('version-list'), - querystring=data.urlencode() + querystring=data.urlencode(), ) resp = self.client.get(url, content_type='application/json') @@ -1515,11 +1531,13 @@ class TaskViewsTests(TestCase): {'data': 'public'}, 'Something bad happened', ) - self.assertEqual(data, { - 'name': 'public_task_exception', - 'data': {'data': 'public'}, - 'started': True, - 'finished': True, - 'success': False, - 'error': 'Something bad happened', - }) + self.assertEqual( + data, { + 'name': 'public_task_exception', + 'data': {'data': 'public'}, + 'started': True, + 'finished': True, + 'success': False, + 'error': 'Something bad happened', + }, + ) diff --git a/readthedocs/rtd_tests/tests/test_api_permissions.py b/readthedocs/rtd_tests/tests/test_api_permissions.py index 5a7d10a0a..7367d8f5d 100644 --- a/readthedocs/rtd_tests/tests/test_api_permissions.py +++ b/readthedocs/rtd_tests/tests/test_api_permissions.py @@ -18,23 +18,27 @@ class APIRestrictedPermissionTests(TestCase): if obj is None: self.assertTrue(handler.has_permission( request=self.get_request(method, is_admin=is_admin), - view=None)) + view=None, + )) else: self.assertTrue(handler.has_object_permission( request=self.get_request(method, is_admin=is_admin), view=None, - obj=obj)) + obj=obj, + )) def assertDisallow(self, handler, method, is_admin, obj=None): if obj is None: self.assertFalse(handler.has_permission( request=self.get_request(method, is_admin=is_admin), - view=None)) + view=None, + )) else: self.assertFalse(handler.has_object_permission( request=self.get_request(method, is_admin=is_admin), view=None, - obj=obj)) + obj=obj, + )) def test_non_object_permissions(self): handler = APIRestrictedPermission() diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py index 8b31de913..5618dd921 100644 --- a/readthedocs/rtd_tests/tests/test_backend.py +++ b/readthedocs/rtd_tests/tests/test_backend.py @@ -33,7 +33,7 @@ class TestGitBackend(RTDTestCase): name='Test Project', repo_type='git', #Our top-level checkout - repo=git_repo + repo=git_repo, ) self.project.users.add(self.eric) self.dummy_conf = Mock() @@ -116,7 +116,7 @@ class TestGitBackend(RTDTestCase): repo.checkout(version) self.assertEqual( str(e.exception), - RepositoryError.FAILED_TO_CHECKOUT.format(version) + RepositoryError.FAILED_TO_CHECKOUT.format(version), ) def test_git_tags(self): @@ -186,7 +186,7 @@ class TestGitBackend(RTDTestCase): # it's a url in ssh form. self.assertEqual( str(e.exception), - RepositoryError.INVALID_SUBMODULES.format(['invalid']) + RepositoryError.INVALID_SUBMODULES.format(['invalid']), ) @patch('readthedocs.projects.models.Project.checkout_path') @@ -209,13 +209,13 @@ class TestGitBackend(RTDTestCase): # We still have all branches and tags in the local repo self.assertEqual( {'v01', 'v02'}, - {vcs.verbose_name for vcs in repo.tags} + {vcs.verbose_name for vcs in repo.tags}, ) self.assertEqual( { 'invalidsubmodule', 'master', 'submodule', 'newbranch', }, - {vcs.verbose_name for vcs in repo.branches} + {vcs.verbose_name for vcs in repo.branches}, ) repo.update() @@ -223,13 +223,13 @@ class TestGitBackend(RTDTestCase): # We don't have the eliminated branches and tags in the local repo self.assertEqual( {'v01'}, - {vcs.verbose_name for vcs in repo.tags} + {vcs.verbose_name for vcs in repo.tags}, ) self.assertEqual( { - 'invalidsubmodule', 'master', 'submodule' + 'invalidsubmodule', 'master', 'submodule', }, - {vcs.verbose_name for vcs in repo.branches} + {vcs.verbose_name for vcs in repo.branches}, ) @@ -245,7 +245,7 @@ class TestHgBackend(RTDTestCase): name='Test Project', repo_type='hg', # Our top-level checkout - repo=hg_repo + repo=hg_repo, ) self.project.users.add(self.eric) @@ -276,7 +276,7 @@ class TestHgBackend(RTDTestCase): repo.checkout(version) self.assertEqual( str(e.exception), - RepositoryError.FAILED_TO_CHECKOUT.format(version) + RepositoryError.FAILED_TO_CHECKOUT.format(version), ) def test_parse_tags(self): diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py index f1129407b..a0fd290b3 100644 --- a/readthedocs/rtd_tests/tests/test_build_config.py +++ b/readthedocs/rtd_tests/tests/test_build_config.py @@ -10,7 +10,7 @@ from readthedocs.config.tests import utils V2_SCHEMA = path.join( path.dirname(__file__), - '../fixtures/spec/v2/schema.yml' + '../fixtures/spec/v2/schema.yml', ) @@ -30,7 +30,7 @@ class PathValidator(Validator): if isinstance(value, str): file_ = path.join( path.dirname(self.configuration_file), - value + value, ) return path.exists(file_) return False @@ -58,7 +58,7 @@ def validate_schema(file): data = yamale.make_data(file) schema = yamale.make_schema( V2_SCHEMA, - validators=validators + validators=validators, ) yamale.validate(schema, data) @@ -84,7 +84,7 @@ def test_invalid_version(tmpdir): assertInvalidConfig( tmpdir, 'version: "latest"', - ['version:', "'latest' not in"] + ['version:', "'latest' not in"], ) @@ -92,7 +92,7 @@ def test_invalid_version_1(tmpdir): assertInvalidConfig( tmpdir, 'version: "1"', - ['version', "'1' not in"] + ['version', "'1' not in"], ) @@ -134,7 +134,7 @@ formats: assertInvalidConfig( tmpdir, content, - ['formats', "'invalidformat' not in"] + ['formats', "'invalidformat' not in"], ) @@ -164,7 +164,7 @@ conda: assertInvalidConfig( tmpdir, content, - ['environment.yaml', 'is not a path'] + ['environment.yaml', 'is not a path'], ) @@ -177,7 +177,7 @@ conda: assertInvalidConfig( tmpdir, content, - ['conda.environment: Required'] + ['conda.environment: Required'], ) @@ -209,7 +209,7 @@ build: assertInvalidConfig( tmpdir, content, - ["build.image: '9.0' not in"] + ["build.image: '9.0' not in"], ) @@ -232,7 +232,7 @@ python: assertInvalidConfig( tmpdir, content, - ["version: '4' not in"] + ["version: '4' not in"], ) @@ -265,7 +265,7 @@ python: assertInvalidConfig( tmpdir, content, - ['requirements:', "'23' is not a path"] + ['requirements:', "'23' is not a path"], ) @@ -291,7 +291,7 @@ python: assertInvalidConfig( tmpdir, content, - ["python.install: 'guido' is not a list"] + ["python.install: 'guido' is not a list"], ) @@ -331,13 +331,15 @@ python: @pytest.mark.parametrize('pipfile', ['another_docs/', '.', 'project/']) def test_python_install_pipfile(tmpdir, pipfile): - utils.apply_fs(tmpdir, { - 'another_docs': { + utils.apply_fs( + tmpdir, { + 'another_docs': { + 'Pipfile': '', + }, + 'project': {}, 'Pipfile': '', }, - 'project': {}, - 'Pipfile': '', - }) + ) content = ''' version: "2" python: @@ -416,7 +418,7 @@ python: assertInvalidConfig( tmpdir, content.format(value=value), - ['is not a bool'] + ['is not a bool'], ) @@ -448,7 +450,7 @@ sphinx: assertInvalidConfig( tmpdir, content, - ['is not a path'] + ['is not a path'], ) @@ -471,7 +473,7 @@ sphinx: assertInvalidConfig( tmpdir, content.format(value=value), - ['is not a bool'] + ['is not a bool'], ) @@ -503,7 +505,7 @@ mkdocs: assertInvalidConfig( tmpdir, content, - ['is not a path'] + ['is not a path'], ) @@ -526,7 +528,7 @@ mkdocs: assertInvalidConfig( tmpdir, content.format(value=value), - ['is not a bool'] + ['is not a bool'], ) @@ -594,7 +596,7 @@ redirects: assertInvalidConfig( tmpdir, content, - ['is not a str'] + ['is not a str'], ) diff --git a/readthedocs/rtd_tests/tests/test_build_forms.py b/readthedocs/rtd_tests/tests/test_build_forms.py index f85ae7419..1e9017716 100644 --- a/readthedocs/rtd_tests/tests/test_build_forms.py +++ b/readthedocs/rtd_tests/tests/test_build_forms.py @@ -28,7 +28,7 @@ class TestVersionForm(TestCase): 'active': True, 'privacy_level': PRIVATE, }, - instance=version + instance=version, ) self.assertTrue(form.is_valid()) @@ -46,7 +46,7 @@ class TestVersionForm(TestCase): 'active': False, 'privacy_level': PRIVATE, }, - instance=version + instance=version, ) self.assertFalse(form.is_valid()) self.assertIn('active', form.errors) diff --git a/readthedocs/rtd_tests/tests/test_build_notifications.py b/readthedocs/rtd_tests/tests/test_build_notifications.py index 4964f32c7..81acedb01 100644 --- a/readthedocs/rtd_tests/tests/test_build_notifications.py +++ b/readthedocs/rtd_tests/tests/test_build_notifications.py @@ -71,7 +71,11 @@ class TestForms(TestCase): self.assertFalse(form.is_valid()) self.assertEqual( form.errors, - {'url': - ['Enter a valid URL.', - 'Ensure this value has at most 600 characters (it has 1507).'] - }) + { + 'url': + [ + 'Enter a valid URL.', + 'Ensure this value has at most 600 characters (it has 1507).', + ], + }, + ) diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index c56117804..afdbe46a3 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -29,16 +29,20 @@ class BuildEnvironmentTests(TestCase): def test_build(self, load_config): """Test full build.""" load_config.side_effect = create_load() - project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - versions=[fixture()]) + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + versions=[fixture()], + ) version = project.versions.all()[0] self.mocks.configure_mock('api_versions', {'return_value': [version]}) - self.mocks.configure_mock('api', { - 'get.return_value': {'downloads': 'no_url_here'} - }) + self.mocks.configure_mock( + 'api', { + 'get.return_value': {'downloads': 'no_url_here'}, + }, + ) self.mocks.patches['html_build'].stop() build_env = LocalBuildEnvironment(project=project, version=version, build={}) @@ -46,7 +50,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) task.build_docs() @@ -60,13 +64,15 @@ class BuildEnvironmentTests(TestCase): def test_build_respects_pdf_flag(self, load_config): """Build output format control.""" load_config.side_effect = create_load() - project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - enable_pdf_build=True, - enable_epub_build=False, - versions=[fixture()]) + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + enable_pdf_build=True, + enable_epub_build=False, + versions=[fixture()], + ) version = project.versions.all()[0] build_env = LocalBuildEnvironment(project=project, version=version, build={}) @@ -74,7 +80,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) task.build_docs() @@ -94,20 +100,20 @@ class BuildEnvironmentTests(TestCase): documentation_type='mkdocs', enable_pdf_build=True, enable_epub_build=True, - versions=[fixture()] + versions=[fixture()], ) version = project.versions.all().first() build_env = LocalBuildEnvironment( project=project, version=version, - build={} + build={}, ) python_env = Virtualenv(version=version, build_env=build_env) config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) task.build_docs() @@ -123,13 +129,15 @@ class BuildEnvironmentTests(TestCase): def test_build_respects_epub_flag(self, load_config): """Test build with epub enabled.""" load_config.side_effect = create_load() - project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - enable_pdf_build=False, - enable_epub_build=True, - versions=[fixture()]) + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + enable_pdf_build=False, + enable_epub_build=True, + versions=[fixture()], + ) version = project.versions.all()[0] build_env = LocalBuildEnvironment(project=project, version=version, build={}) @@ -137,7 +145,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) task.build_docs() @@ -151,13 +159,15 @@ class BuildEnvironmentTests(TestCase): def test_build_respects_yaml(self, load_config): """Test YAML build options.""" load_config.side_effect = create_load({'formats': ['epub']}) - project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - enable_pdf_build=False, - enable_epub_build=False, - versions=[fixture()]) + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()], + ) version = project.versions.all()[0] build_env = LocalBuildEnvironment(project=project, version=version, build={}) @@ -166,7 +176,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) task.build_docs() @@ -184,13 +194,15 @@ class BuildEnvironmentTests(TestCase): self.mocks.patches['html_build'].stop() self.mocks.patches['pdf_build'].stop() - project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - enable_pdf_build=True, - enable_epub_build=False, - versions=[fixture()]) + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + enable_pdf_build=True, + enable_epub_build=False, + versions=[fixture()], + ) version = project.versions.all()[0] assert project.conf_dir() == '/tmp/rtd' @@ -199,7 +211,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) # Mock out the separate calls to Popen using an iterable side_effect @@ -211,10 +223,13 @@ class BuildEnvironmentTests(TestCase): ((b'', b''), 0), # latex ] mock_obj = mock.Mock() - mock_obj.communicate.side_effect = [output for (output, status) - in returns] + mock_obj.communicate.side_effect = [ + output for (output, status) + in returns + ] type(mock_obj).returncode = mock.PropertyMock( - side_effect=[status for (output, status) in returns]) + side_effect=[status for (output, status) in returns], + ) self.mocks.popen.return_value = mock_obj with build_env: @@ -230,13 +245,15 @@ class BuildEnvironmentTests(TestCase): self.mocks.patches['html_build'].stop() self.mocks.patches['pdf_build'].stop() - project = get(Project, - slug='project-2', - documentation_type='sphinx', - conf_py_file='test_conf.py', - enable_pdf_build=True, - enable_epub_build=False, - versions=[fixture()]) + project = get( + Project, + slug='project-2', + documentation_type='sphinx', + conf_py_file='test_conf.py', + enable_pdf_build=True, + enable_epub_build=False, + versions=[fixture()], + ) version = project.versions.all()[0] assert project.conf_dir() == '/tmp/rtd' @@ -245,7 +262,7 @@ class BuildEnvironmentTests(TestCase): config = load_yaml_config(version) task = UpdateDocsTaskStep( build_env=build_env, project=project, python_env=python_env, - version=version, config=config + version=version, config=config, ) # Mock out the separate calls to Popen using an iterable side_effect @@ -257,10 +274,13 @@ class BuildEnvironmentTests(TestCase): ((b'', b''), 0), # latex ] mock_obj = mock.Mock() - mock_obj.communicate.side_effect = [output for (output, status) - in returns] + mock_obj.communicate.side_effect = [ + output for (output, status) + in returns + ] type(mock_obj).returncode = mock.PropertyMock( - side_effect=[status for (output, status) in returns]) + side_effect=[status for (output, status) in returns], + ) self.mocks.popen.return_value = mock_obj with build_env: @@ -281,7 +301,7 @@ class BuildEnvironmentTests(TestCase): build = get(Build) version = get(Version, slug='1.8', project=project) task = UpdateDocsTaskStep( - project=project, version=version, build={'id': build.pk} + project=project, version=version, build={'id': build.pk}, ) task.setup_vcs = mock.Mock() task.run_setup() @@ -353,13 +373,13 @@ class BuildModelTests(TestCase): Build, project=self.project, version=self.version, - config={'version': 1} + config={'version': 1}, ) build_two = get( Build, project=self.project, version=self.version, - config={'version': 2} + config={'version': 2}, ) build_three = get( Build, diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index 48613710a..5ac9fac88 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -78,14 +78,17 @@ class TestCeleryBuilding(RTDTestCase): @patch('readthedocs.projects.tasks.UpdateDocsTaskStep.build_docs', new=MagicMock) @patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_vcs', new=MagicMock) def test_update_docs(self): - build = get(Build, project=self.project, - version=self.project.versions.first()) + build = get( + Build, project=self.project, + version=self.project.versions.first(), + ) with mock_api(self.repo) as mapi: result = tasks.update_docs_task.delay( self.project.pk, build_pk=build.pk, record=False, - intersphinx=False) + intersphinx=False, + ) self.assertTrue(result.successful()) @patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_python_environment', new=MagicMock) @@ -95,14 +98,17 @@ class TestCeleryBuilding(RTDTestCase): def test_update_docs_unexpected_setup_exception(self, mock_setup_vcs): exc = Exception() mock_setup_vcs.side_effect = exc - build = get(Build, project=self.project, - version=self.project.versions.first()) + build = get( + Build, project=self.project, + version=self.project.versions.first(), + ) with mock_api(self.repo) as mapi: result = tasks.update_docs_task.delay( self.project.pk, build_pk=build.pk, record=False, - intersphinx=False) + intersphinx=False, + ) self.assertTrue(result.successful()) @patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_python_environment', new=MagicMock) @@ -112,14 +118,17 @@ class TestCeleryBuilding(RTDTestCase): def test_update_docs_unexpected_build_exception(self, mock_build_docs): exc = Exception() mock_build_docs.side_effect = exc - build = get(Build, project=self.project, - version=self.project.versions.first()) + build = get( + Build, project=self.project, + version=self.project.versions.first(), + ) with mock_api(self.repo) as mapi: result = tasks.update_docs_task.delay( self.project.pk, build_pk=build.pk, record=False, - intersphinx=False) + intersphinx=False, + ) self.assertTrue(result.successful()) @patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_python_environment', new=MagicMock) @@ -129,14 +138,17 @@ class TestCeleryBuilding(RTDTestCase): def test_no_notification_on_version_locked_error(self, mock_setup_vcs, mock_send_notifications): mock_setup_vcs.side_effect = VersionLockedError() - build = get(Build, project=self.project, - version=self.project.versions.first()) + build = get( + Build, project=self.project, + version=self.project.versions.first(), + ) with mock_api(self.repo) as mapi: result = tasks.update_docs_task.delay( self.project.pk, build_pk=build.pk, record=False, - intersphinx=False) + intersphinx=False, + ) mock_send_notifications.assert_not_called() self.assertTrue(result.successful()) @@ -166,7 +178,7 @@ class TestCeleryBuilding(RTDTestCase): sync_repository.sync_repo() self.assertEqual( str(e.exception), - RepositoryError.DUPLICATED_RESERVED_VERSIONS + RepositoryError.DUPLICATED_RESERVED_VERSIONS, ) delete_git_branch(self.repo, 'latest') @@ -192,7 +204,7 @@ class TestCeleryBuilding(RTDTestCase): sync_repository.sync_repo() self.assertEqual( str(e.exception), - RepositoryError.DUPLICATED_RESERVED_VERSIONS + RepositoryError.DUPLICATED_RESERVED_VERSIONS, ) # TODO: Check that we can build properly after @@ -230,9 +242,11 @@ class TestCeleryBuilding(RTDTestCase): # although the task risen an exception, it's success since we add the # exception into the ``info`` attributes self.assertEqual(result.status, 'SUCCESS') - self.assertEqual(result.info, { - 'task_name': 'public_task_exception', - 'context': {}, - 'public_data': {}, - 'error': 'Something bad happened', - }) + self.assertEqual( + result.info, { + 'task_name': 'public_task_exception', + 'context': {}, + 'public_data': {}, + 'error': 'Something bad happened', + }, + ) diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py index 2df44a08e..e09e82199 100644 --- a/readthedocs/rtd_tests/tests/test_config_integration.py +++ b/readthedocs/rtd_tests/tests/test_config_integration.py @@ -90,7 +90,7 @@ class LoadConfigTests(TestCase): 'formats': [ 'htmlzip', 'epub', - 'pdf' + 'pdf', ], 'use_system_packages': self.project.use_system_packages, 'requirements_file': self.project.requirements_file, @@ -110,8 +110,10 @@ class LoadConfigTests(TestCase): self.project.container_image = 'readthedocs/build:1.0' self.project.save() config = load_yaml_config(self.version) - self.assertEqual(config.get_valid_python_versions(), - [2, 2.7, 3, 3.4]) + self.assertEqual( + config.get_valid_python_versions(), + [2, 2.7, 3, 3.4], + ) @mock.patch('readthedocs.doc_builder.config.load_config') def test_python_supported_versions_image_2_0(self, load_config): @@ -119,8 +121,10 @@ class LoadConfigTests(TestCase): self.project.container_image = 'readthedocs/build:2.0' self.project.save() config = load_yaml_config(self.version) - self.assertEqual(config.get_valid_python_versions(), - [2, 2.7, 3, 3.5]) + self.assertEqual( + config.get_valid_python_versions(), + [2, 2.7, 3, 3.5], + ) @mock.patch('readthedocs.doc_builder.config.load_config') def test_python_supported_versions_image_latest(self, load_config): @@ -128,8 +132,10 @@ class LoadConfigTests(TestCase): self.project.container_image = 'readthedocs/build:latest' self.project.save() config = load_yaml_config(self.version) - self.assertEqual(config.get_valid_python_versions(), - [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6]) + self.assertEqual( + config.get_valid_python_versions(), + [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6], + ) @mock.patch('readthedocs.doc_builder.config.load_config') def test_python_default_version(self, load_config): @@ -162,7 +168,7 @@ class LoadConfigTests(TestCase): @mock.patch('readthedocs.doc_builder.config.load_config') def test_python_invalid_version_in_config(self, load_config): load_config.side_effect = create_load({ - 'python': {'version': 2.6} + 'python': {'version': 2.6}, }) self.project.container_image = 'readthedocs/build:2.0' self.project.save() @@ -175,11 +181,11 @@ class LoadConfigTests(TestCase): config = load_yaml_config(self.version) self.assertEqual( config.python.install_with_pip or config.python.install_with_setup, - False + False, ) load_config.side_effect = create_load({ - 'python': {'setup_py_install': True} + 'python': {'setup_py_install': True}, }) config = load_yaml_config(self.version) self.assertEqual(config.python.install_with_setup, True) @@ -189,16 +195,16 @@ class LoadConfigTests(TestCase): load_config.side_effect = create_load({ 'python': { 'pip_install': True, - 'extra_requirements': ['tests', 'docs'] - } + 'extra_requirements': ['tests', 'docs'], + }, }) config = load_yaml_config(self.version) self.assertEqual(config.python.extra_requirements, ['tests', 'docs']) load_config.side_effect = create_load({ 'python': { - 'extra_requirements': ['tests', 'docs'] - } + 'extra_requirements': ['tests', 'docs'], + }, }) config = load_yaml_config(self.version) self.assertEqual(config.python.extra_requirements, []) @@ -210,8 +216,8 @@ class LoadConfigTests(TestCase): load_config.side_effect = create_load({ 'python': { 'setup_py_install': True, - 'extra_requirements': ['tests', 'docs'] - } + 'extra_requirements': ['tests', 'docs'], + }, }) config = load_yaml_config(self.version) self.assertEqual(config.python.extra_requirements, []) @@ -228,7 +234,7 @@ class LoadConfigTests(TestCase): { 'conda': { 'file': conda_file, - } + }, }, base_path=base_path, ) @@ -302,9 +308,11 @@ class TestLoadConfigV2: ) def create_config_file(self, tmpdir, config): - base_path = apply_fs(tmpdir, { - 'readthedocs.yml': '', - }) + base_path = apply_fs( + tmpdir, { + 'readthedocs.yml': '', + }, + ) config.setdefault('version', 2) config_file = path.join(str(base_path), 'readthedocs.yml') yaml.safe_dump(config, open(config_file, 'w')) @@ -312,7 +320,7 @@ class TestLoadConfigV2: def get_update_docs_task(self): build_env = LocalBuildEnvironment( - self.project, self.version, record=False + self.project, self.version, record=False, ) update_docs = tasks.UpdateDocsTaskStep( @@ -341,7 +349,8 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.HtmlBuilder.build') @patch('readthedocs.doc_builder.backends.sphinx.HtmlBuilder.append_conf') def test_build_formats_default_empty( - self, append_conf, html_build, checkout_path, config, tmpdir): + self, append_conf, html_build, checkout_path, config, tmpdir, + ): """ The default value for formats is [], which means no extra formats are build. @@ -353,7 +362,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=update_docs.config + config=update_docs.config, ) update_docs.python_env = python_env outcomes = update_docs.build_docs() @@ -370,7 +379,8 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.HtmlBuilder.append_conf') def test_build_formats_only_pdf( self, append_conf, html_build, build_docs_class, - checkout_path, tmpdir): + checkout_path, tmpdir, + ): """Only the pdf format is build.""" checkout_path.return_value = str(tmpdir) self.create_config_file(tmpdir, {'formats': ['pdf']}) @@ -379,7 +389,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=update_docs.config + config=update_docs.config, ) update_docs.python_env = python_env @@ -403,8 +413,8 @@ class TestLoadConfigV2: base_path = self.create_config_file( tmpdir, { - 'conda': {'environment': conda_file} - } + 'conda': {'environment': conda_file}, + }, ) update_docs = self.get_update_docs_task() @@ -461,8 +471,8 @@ class TestLoadConfigV2: base_path = self.create_config_file( tmpdir, { - 'python': {'requirements': requirements_file} - } + 'python': {'requirements': requirements_file}, + }, ) update_docs = self.get_update_docs_task() @@ -471,7 +481,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.install_user_requirements() @@ -488,8 +498,8 @@ class TestLoadConfigV2: self.create_config_file( tmpdir, { - 'python': {'requirements': ''} - } + 'python': {'requirements': ''}, + }, ) update_docs = self.get_update_docs_task() @@ -498,7 +508,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.install_user_requirements() @@ -512,8 +522,8 @@ class TestLoadConfigV2: self.create_config_file( tmpdir, { - 'python': {'install': 'setup.py'} - } + 'python': {'install': 'setup.py'}, + }, ) update_docs = self.get_update_docs_task() @@ -522,7 +532,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.install_package() @@ -540,8 +550,8 @@ class TestLoadConfigV2: self.create_config_file( tmpdir, { - 'python': {'install': 'pip'} - } + 'python': {'install': 'pip'}, + }, ) update_docs = self.get_update_docs_task() @@ -550,7 +560,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.install_package() @@ -583,8 +593,8 @@ class TestLoadConfigV2: 'python': { 'install': 'pip', 'extra_requirements': ['docs'], - } - } + }, + }, ) update_docs = self.get_update_docs_task() @@ -593,7 +603,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.install_package() @@ -614,8 +624,8 @@ class TestLoadConfigV2: { 'python': { 'system_packages': True, - } - } + }, + }, ) update_docs = self.get_update_docs_task() @@ -624,7 +634,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env update_docs.python_env.setup_base() @@ -634,13 +644,18 @@ class TestLoadConfigV2: assert '--system-site-packages' in args assert config.python.use_system_site_packages - @pytest.mark.parametrize('value,result', - [('html', 'sphinx'), - ('htmldir', 'sphinx_htmldir'), - ('singlehtml', 'sphinx_singlehtml')]) + @pytest.mark.parametrize( + 'value,result', + [ + ('html', 'sphinx'), + ('htmldir', 'sphinx_htmldir'), + ('singlehtml', 'sphinx_singlehtml'), + ], + ) @patch('readthedocs.projects.tasks.get_builder_class') def test_sphinx_builder( - self, get_builder_class, checkout_path, value, result, tmpdir): + self, get_builder_class, checkout_path, value, result, tmpdir, + ): checkout_path.return_value = str(tmpdir) self.create_config_file(tmpdir, {'sphinx': {'builder': value}}) @@ -653,10 +668,12 @@ class TestLoadConfigV2: get_builder_class.assert_called_with(result) @pytest.mark.skip( - 'This test is not compatible with the new validation around doctype.') + 'This test is not compatible with the new validation around doctype.', + ) @patch('readthedocs.projects.tasks.get_builder_class') def test_sphinx_builder_default( - self, get_builder_class, checkout_path, tmpdir): + self, get_builder_class, checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) self.create_config_file(tmpdir, {}) @@ -672,7 +689,8 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.run') def test_sphinx_configuration_default( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): """Should be default to find a conf.py file.""" checkout_path.return_value = str(tmpdir) @@ -686,7 +704,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -701,7 +719,8 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.run') def test_sphinx_configuration_default( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): """Should be default to find a conf.py file.""" checkout_path.return_value = str(tmpdir) @@ -715,7 +734,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -730,21 +749,24 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.run') def test_sphinx_configuration( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) - apply_fs(tmpdir, { - 'conf.py': '', - 'docx': { + apply_fs( + tmpdir, { 'conf.py': '', + 'docx': { + 'conf.py': '', + }, }, - }) + ) self.create_config_file( tmpdir, { 'sphinx': { 'configuration': 'docx/conf.py', }, - } + }, ) update_docs = self.get_update_docs_task() @@ -752,7 +774,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -767,13 +789,16 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.append_conf') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.run') def test_sphinx_fail_on_warning( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) - apply_fs(tmpdir, { - 'docx': { - 'conf.py': '', + apply_fs( + tmpdir, { + 'docx': { + 'conf.py': '', + }, }, - }) + ) self.create_config_file( tmpdir, { @@ -781,7 +806,7 @@ class TestLoadConfigV2: 'configuration': 'docx/conf.py', 'fail_on_warning': True, }, - } + }, ) update_docs = self.get_update_docs_task() @@ -789,7 +814,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -804,21 +829,24 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.append_conf') @patch('readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.run') def test_mkdocs_configuration( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) - apply_fs(tmpdir, { - 'mkdocs.yml': '', - 'docx': { + apply_fs( + tmpdir, { 'mkdocs.yml': '', + 'docx': { + 'mkdocs.yml': '', + }, }, - }) + ) self.create_config_file( tmpdir, { 'mkdocs': { 'configuration': 'docx/mkdocs.yml', }, - } + }, ) self.project.documentation_type = 'mkdocs' self.project.save() @@ -828,7 +856,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -844,13 +872,16 @@ class TestLoadConfigV2: @patch('readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.append_conf') @patch('readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.run') def test_mkdocs_fail_on_warning( - self, run, append_conf, move, checkout_path, tmpdir): + self, run, append_conf, move, checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) - apply_fs(tmpdir, { - 'docx': { - 'mkdocs.yml': '', + apply_fs( + tmpdir, { + 'docx': { + 'mkdocs.yml': '', + }, }, - }) + ) self.create_config_file( tmpdir, { @@ -858,7 +889,7 @@ class TestLoadConfigV2: 'configuration': 'docx/mkdocs.yml', 'fail_on_warning': True, }, - } + }, ) self.project.documentation_type = 'mkdocs' self.project.save() @@ -868,7 +899,7 @@ class TestLoadConfigV2: python_env = Virtualenv( version=self.version, build_env=update_docs.build_env, - config=config + config=config, ) update_docs.python_env = python_env @@ -879,11 +910,17 @@ class TestLoadConfigV2: append_conf.assert_called_once() move.assert_called_once() - @pytest.mark.parametrize('value,expected', [(ALL, ['one', 'two', 'three']), - (['one', 'two'], ['one', 'two'])]) + @pytest.mark.parametrize( + 'value,expected', [ + (ALL, ['one', 'two', 'three']), + (['one', 'two'], ['one', 'two']), + ], + ) @patch('readthedocs.vcs_support.backends.git.Backend.checkout_submodules') - def test_submodules_include(self, checkout_submodules, - checkout_path, tmpdir, value, expected): + def test_submodules_include( + self, checkout_submodules, + checkout_path, tmpdir, value, expected, + ): checkout_path.return_value = str(tmpdir) self.create_config_file( tmpdir, @@ -891,7 +928,7 @@ class TestLoadConfigV2: 'submodules': { 'include': value, }, - } + }, ) git_repo = make_git_repo(str(tmpdir)) @@ -908,8 +945,10 @@ class TestLoadConfigV2: assert update_docs.config.submodules.recursive is False @patch('readthedocs.vcs_support.backends.git.Backend.checkout_submodules') - def test_submodules_exclude(self, checkout_submodules, - checkout_path, tmpdir): + def test_submodules_exclude( + self, checkout_submodules, + checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) self.create_config_file( tmpdir, @@ -918,7 +957,7 @@ class TestLoadConfigV2: 'exclude': ['one'], 'recursive': True, }, - } + }, ) git_repo = make_git_repo(str(tmpdir)) @@ -935,8 +974,10 @@ class TestLoadConfigV2: assert update_docs.config.submodules.recursive is True @patch('readthedocs.vcs_support.backends.git.Backend.checkout_submodules') - def test_submodules_exclude_all(self, checkout_submodules, - checkout_path, tmpdir): + def test_submodules_exclude_all( + self, checkout_submodules, + checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) self.create_config_file( tmpdir, @@ -945,7 +986,7 @@ class TestLoadConfigV2: 'exclude': ALL, 'recursive': True, }, - } + }, ) git_repo = make_git_repo(str(tmpdir)) @@ -960,13 +1001,15 @@ class TestLoadConfigV2: checkout_submodules.assert_not_called() @patch('readthedocs.vcs_support.backends.git.Backend.checkout_submodules') - def test_submodules_default_exclude_all(self, checkout_submodules, - checkout_path, tmpdir): + def test_submodules_default_exclude_all( + self, checkout_submodules, + checkout_path, tmpdir, + ): checkout_path.return_value = str(tmpdir) self.create_config_file( tmpdir, - {} + {}, ) git_repo = make_git_repo(str(tmpdir)) diff --git a/readthedocs/rtd_tests/tests/test_core_utils.py b/readthedocs/rtd_tests/tests/test_core_utils.py index a19b5db6a..e57b46c0b 100644 --- a/readthedocs/rtd_tests/tests/test_core_utils.py +++ b/readthedocs/rtd_tests/tests/test_core_utils.py @@ -133,13 +133,23 @@ class CoreUtilTests(TestCase): def test_slugify(self): """Test additional slugify.""" - self.assertEqual(slugify('This is a test'), - 'this-is-a-test') - self.assertEqual(slugify('project_with_underscores-v.1.0'), - 'project-with-underscores-v10') - self.assertEqual(slugify('project_with_underscores-v.1.0', dns_safe=False), - 'project_with_underscores-v10') - self.assertEqual(slugify('A title_-_with separated parts'), - 'a-title-with-separated-parts') - self.assertEqual(slugify('A title_-_with separated parts', dns_safe=False), - 'a-title_-_with-separated-parts') + self.assertEqual( + slugify('This is a test'), + 'this-is-a-test', + ) + self.assertEqual( + slugify('project_with_underscores-v.1.0'), + 'project-with-underscores-v10', + ) + self.assertEqual( + slugify('project_with_underscores-v.1.0', dns_safe=False), + 'project_with_underscores-v10', + ) + self.assertEqual( + slugify('A title_-_with separated parts'), + 'a-title-with-separated-parts', + ) + self.assertEqual( + slugify('A title_-_with separated parts', dns_safe=False), + 'a-title_-_with-separated-parts', + ) diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index ff740302b..57bd6a154 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -61,12 +61,12 @@ class SphinxBuilderTest(TestCase): for value, expected in (('conf.py', '/'), ('docs/conf.py', '/docs/')): base_sphinx.config_file = os.path.join( - tmp_dir, value + tmp_dir, value, ) params = base_sphinx.get_config_params() self.assertEqual( params['conf_py_path'], - expected + expected, ) @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') @@ -77,7 +77,8 @@ class SphinxBuilderTest(TestCase): @patch('readthedocs.projects.models.Project.checkout_path') def test_create_conf_py( self, checkout_path, get_conf_py_path, _, - get_config_params, create_index, docs_dir): + get_config_params, create_index, docs_dir, + ): """ Test for a project without ``conf.py`` file. @@ -116,13 +117,13 @@ class SphinxBuilderTest(TestCase): os.path.dirname(__file__), '..', 'files', - 'conf.py' + 'conf.py', ) with open(generated_conf_py) as gf, open(expected_conf_py) as ef: autogenerated_confpy_lines = 28 self.assertEqual( gf.readlines()[:autogenerated_confpy_lines], - ef.readlines()[:autogenerated_confpy_lines] + ef.readlines()[:autogenerated_confpy_lines], ) @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') @@ -133,7 +134,8 @@ class SphinxBuilderTest(TestCase): @patch('readthedocs.projects.models.Project.checkout_path') def test_multiple_conf_py( self, checkout_path, get_conf_py_path, _, get_config_params, - create_index, docs_dir): + create_index, docs_dir, + ): """ Test for a project with multiple ``conf.py`` files. @@ -304,14 +306,14 @@ class MkdocsBuilderTest(TestCase): config = yaml.safe_load(open(generated_yaml)) self.assertEqual( config['docs_dir'], - os.path.join(tmpdir, 'docs') + os.path.join(tmpdir, 'docs'), ) self.assertEqual( config['extra_css'], [ 'http://readthedocs.org/static/css/badge_only.css', - 'http://readthedocs.org/static/css/readthedocs-doc-embed.css' - ] + 'http://readthedocs.org/static/css/readthedocs-doc-embed.css', + ], ) self.assertEqual( config['extra_javascript'], @@ -319,14 +321,14 @@ class MkdocsBuilderTest(TestCase): 'readthedocs-data.js', 'http://readthedocs.org/static/core/js/readthedocs-doc-embed.js', 'http://readthedocs.org/static/javascript/readthedocs-analytics.js', - ] + ], ) self.assertIsNone( config['google_analytics'], ) self.assertEqual( config['site_name'], - 'mkdocs' + 'mkdocs', ) @patch('readthedocs.doc_builder.base.BaseBuilder.run') @@ -341,7 +343,7 @@ class MkdocsBuilderTest(TestCase): 'google_analytics': ['UA-1234-5', 'mkdocs.org'], 'docs_dir': 'docs', }, - open(yaml_file, 'w') + open(yaml_file, 'w'), ) checkout_path.return_value = tmpdir @@ -361,14 +363,14 @@ class MkdocsBuilderTest(TestCase): config = yaml.safe_load(open(yaml_file)) self.assertEqual( config['docs_dir'], - 'docs' + 'docs', ) self.assertEqual( config['extra_css'], [ 'http://readthedocs.org/static/css/badge_only.css', - 'http://readthedocs.org/static/css/readthedocs-doc-embed.css' - ] + 'http://readthedocs.org/static/css/readthedocs-doc-embed.css', + ], ) self.assertEqual( config['extra_javascript'], @@ -376,14 +378,14 @@ class MkdocsBuilderTest(TestCase): 'readthedocs-data.js', 'http://readthedocs.org/static/core/js/readthedocs-doc-embed.js', 'http://readthedocs.org/static/javascript/readthedocs-analytics.js', - ] + ], ) self.assertIsNone( config['google_analytics'], ) self.assertEqual( config['site_name'], - 'mkdocs' + 'mkdocs', ) @patch('readthedocs.doc_builder.base.BaseBuilder.run') @@ -432,7 +434,7 @@ class MkdocsBuilderTest(TestCase): 'site_name': 'mkdocs', 'docs_dir': 'docs', }, - open(yaml_file, 'w') + open(yaml_file, 'w'), ) checkout_path.return_value = tmpdir @@ -452,7 +454,7 @@ class MkdocsBuilderTest(TestCase): config = yaml.safe_load(open(yaml_file)) self.assertEqual( config['theme_dir'], - 'not-readthedocs' + 'not-readthedocs', ) @patch('readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.generate_rtd_data') @@ -467,7 +469,7 @@ class MkdocsBuilderTest(TestCase): 'site_name': 'mkdocs', 'docs_dir': 'docs', }, - open(yaml_file, 'w') + open(yaml_file, 'w'), ) checkout_path.return_value = tmpdir generate_rtd_data.return_value = '' @@ -485,5 +487,5 @@ class MkdocsBuilderTest(TestCase): generate_rtd_data.assert_called_with( docs_dir='docs', - mkdocs_config=mock.ANY + mkdocs_config=mock.ANY, ) diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 91e27907a..25ce88bcb 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -58,9 +58,11 @@ class TestLocalBuildEnvironment(TestCase): def test_normal_execution(self): """Normal build in passing state.""" - self.mocks.configure_mock('process', { - 'communicate.return_value': (b'This is okay', '') - }) + self.mocks.configure_mock( + 'process', { + 'communicate.return_value': (b'This is okay', ''), + }, + ) type(self.mocks.process).returncode = PropertyMock(return_value=0) build_env = LocalBuildEnvironment( @@ -107,9 +109,11 @@ class TestLocalBuildEnvironment(TestCase): def test_command_not_recorded(self): """Normal build in passing state with no command recorded.""" - self.mocks.configure_mock('process', { - 'communicate.return_value': (b'This is okay', '') - }) + self.mocks.configure_mock( + 'process', { + 'communicate.return_value': (b'This is okay', ''), + }, + ) type(self.mocks.process).returncode = PropertyMock(return_value=0) build_env = LocalBuildEnvironment( @@ -144,9 +148,11 @@ class TestLocalBuildEnvironment(TestCase): }) def test_record_command_as_success(self): - self.mocks.configure_mock('process', { - 'communicate.return_value': (b'This is okay', '') - }) + self.mocks.configure_mock( + 'process', { + 'communicate.return_value': (b'This is okay', ''), + }, + ) type(self.mocks.process).returncode = PropertyMock(return_value=1) build_env = LocalBuildEnvironment( @@ -228,9 +234,11 @@ class TestLocalBuildEnvironment(TestCase): def test_failing_execution(self): """Build in failing state.""" - self.mocks.configure_mock('process', { - 'communicate.return_value': (b'This is not okay', '') - }) + self.mocks.configure_mock( + 'process', { + 'communicate.return_value': (b'This is not okay', ''), + }, + ) type(self.mocks.process).returncode = PropertyMock(return_value=1) build_env = LocalBuildEnvironment( @@ -505,8 +513,10 @@ class TestDockerBuildEnvironment(TestCase): 'docker_client', { 'create_container.side_effect': DockerAPIError( 'Failure creating container', response, - 'Failure creating container') - }) + 'Failure creating container', + ), + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -546,8 +556,10 @@ class TestDockerBuildEnvironment(TestCase): 'docker_client', { 'exec_create.side_effect': DockerAPIError( 'Failure creating container', response, - 'Failure creating container'), - }) + 'Failure creating container', + ), + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -592,9 +604,11 @@ class TestDockerBuildEnvironment(TestCase): def test_api_failure_on_error_in_exit(self): response = Mock(status_code=500, reason='Internal Server Error') - self.mocks.configure_mock('docker_client', { - 'kill.side_effect': BuildEnvironmentError('Failed') - }) + self.mocks.configure_mock( + 'docker_client', { + 'kill.side_effect': BuildEnvironmentError('Failed'), + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -632,9 +646,11 @@ class TestDockerBuildEnvironment(TestCase): usable error to show the user. """ response = Mock(status_code=500, reason='Internal Server Error') - self.mocks.configure_mock('docker_client', { - 'kill.side_effect': BuildEnvironmentError('Outer failed') - }) + self.mocks.configure_mock( + 'docker_client', { + 'kill.side_effect': BuildEnvironmentError('Outer failed'), + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -671,7 +687,8 @@ class TestDockerBuildEnvironment(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'This is the return', 'exec_inspect.return_value': {'ExitCode': 1}, - }) + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -684,7 +701,8 @@ class TestDockerBuildEnvironment(TestCase): self.mocks.docker_client.exec_create.assert_called_with( container='build-123-project-6-pip', - cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True) + cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True, + ) self.assertEqual(build_env.commands[0].exit_code, 1) self.assertEqual(build_env.commands[0].output, 'This is the return') self.assertEqual(build_env.commands[0].error, None) @@ -725,7 +743,8 @@ class TestDockerBuildEnvironment(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'This is the return', 'exec_inspect.return_value': {'ExitCode': 1}, - }) + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -738,7 +757,8 @@ class TestDockerBuildEnvironment(TestCase): self.mocks.docker_client.exec_create.assert_called_with( container='build-123-project-6-pip', - cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True) + cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True, + ) self.assertEqual(len(build_env.commands), 0) self.assertFalse(build_env.failed) @@ -766,7 +786,8 @@ class TestDockerBuildEnvironment(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'This is the return', 'exec_inspect.return_value': {'ExitCode': 1}, - }) + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -779,7 +800,8 @@ class TestDockerBuildEnvironment(TestCase): self.mocks.docker_client.exec_create.assert_called_with( container='build-123-project-6-pip', - cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True) + cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True, + ) self.assertEqual(build_env.commands[0].exit_code, 0) self.assertEqual(build_env.commands[0].output, 'This is the return') self.assertEqual(build_env.commands[0].error, None) @@ -825,8 +847,9 @@ class TestDockerBuildEnvironment(TestCase): 'Failure killing container', response, 'Failure killing container', - ) - }) + ), + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -837,7 +860,8 @@ class TestDockerBuildEnvironment(TestCase): build_env.run('echo', 'test', cwd='/tmp') self.mocks.docker_client.kill.assert_called_with( - 'build-123-project-6-pip') + 'build-123-project-6-pip', + ) self.assertTrue(build_env.successful) # api() is not called anymore, we use api_v2 instead @@ -876,7 +900,8 @@ class TestDockerBuildEnvironment(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'This is the return', 'exec_inspect.return_value': {'ExitCode': 0}, - }) + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -891,7 +916,8 @@ class TestDockerBuildEnvironment(TestCase): self.assertRaises(BuildEnvironmentError, _inner) self.assertEqual( str(build_env.failure), - 'A build environment is currently running for this version') + 'A build environment is currently running for this version', + ) self.assertEqual(self.mocks.docker_client.exec_create.call_count, 0) self.assertTrue(build_env.failed) @@ -930,7 +956,8 @@ class TestDockerBuildEnvironment(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'This is the return', 'exec_inspect.return_value': {'ExitCode': 0}, - }) + }, + ) build_env = DockerBuildEnvironment( version=self.version, @@ -1032,8 +1059,10 @@ class TestBuildCommand(TestCase): self.assertEqual(cmd.output, 'FOOBAR') self.assertIsNone(cmd.error) # Test non-combined streams - cmd = BuildCommand(['/bin/bash', '-c', 'echo -n FOOBAR 1>&2'], - combine_output=False) + cmd = BuildCommand( + ['/bin/bash', '-c', 'echo -n FOOBAR 1>&2'], + combine_output=False, + ) cmd.run() self.assertEqual(cmd.output, '') self.assertEqual(cmd.error, 'FOOBAR') @@ -1060,7 +1089,8 @@ class TestBuildCommand(TestCase): cmd.run() self.assertEqual( cmd.output, - 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') + 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9', + ) class TestDockerBuildCommand(TestCase): @@ -1076,8 +1106,10 @@ class TestDockerBuildCommand(TestCase): def test_wrapped_command(self): """Test shell wrapping for Docker chdir.""" - cmd = DockerBuildCommand(['pip', 'install', 'requests'], - cwd='/tmp/foobar') + cmd = DockerBuildCommand( + ['pip', 'install', 'requests'], + cwd='/tmp/foobar', + ) self.assertEqual( cmd.get_wrapped_command(), "/bin/sh -c 'cd /tmp/foobar && pip install requests'", @@ -1089,9 +1121,11 @@ class TestDockerBuildCommand(TestCase): ) self.assertEqual( cmd.get_wrapped_command(), - ('/bin/sh -c ' - "'cd /tmp/foobar && PATH=/tmp/foo:$PATH " - r"python /tmp/foo/pip install Django\>1.7'"), + ( + '/bin/sh -c ' + "'cd /tmp/foobar && PATH=/tmp/foo:$PATH " + r"python /tmp/foo/pip install Django\>1.7'" + ), ) def test_unicode_output(self): @@ -1101,7 +1135,8 @@ class TestDockerBuildCommand(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': SAMPLE_UTF8_BYTES, 'exec_inspect.return_value': {'ExitCode': 0}, - }) + }, + ) cmd = DockerBuildCommand(['echo', 'test'], cwd='/tmp/foobar') cmd.build_env = Mock() cmd.build_env.get_client.return_value = self.mocks.docker_client @@ -1109,7 +1144,8 @@ class TestDockerBuildCommand(TestCase): cmd.run() self.assertEqual( cmd.output, - 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9') + 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9', + ) self.assertEqual(self.mocks.docker_client.exec_start.call_count, 1) self.assertEqual(self.mocks.docker_client.exec_create.call_count, 1) self.assertEqual(self.mocks.docker_client.exec_inspect.call_count, 1) @@ -1121,7 +1157,8 @@ class TestDockerBuildCommand(TestCase): 'exec_create.return_value': {'Id': b'container-foobar'}, 'exec_start.return_value': b'Killed\n', 'exec_inspect.return_value': {'ExitCode': 137}, - }) + }, + ) cmd = DockerBuildCommand(['echo', 'test'], cwd='/tmp/foobar') cmd.build_env = Mock() cmd.build_env.get_client.return_value = self.mocks.docker_client @@ -1129,7 +1166,7 @@ class TestDockerBuildCommand(TestCase): cmd.run() self.assertIn( 'Command killed due to excessive memory consumption\n', - str(cmd.output) + str(cmd.output), ) @@ -1204,7 +1241,7 @@ class TestPythonEnvironment(TestCase): checkout_path.return_value = tmpdir python_env = Virtualenv( version=self.version_mkdocs, - build_env=self.build_env_mock + build_env=self.build_env_mock, ) python_env.install_core_requirements() requirements_mkdocs = [ @@ -1235,15 +1272,15 @@ class TestPythonEnvironment(TestCase): self.build_env_mock.version = self.version_sphinx python_env = Virtualenv( version=self.version_sphinx, - build_env=self.build_env_mock + build_env=self.build_env_mock, ) checkout_path = python_env.checkout_path docs_requirements = os.path.join( - checkout_path, 'docs', 'requirements.txt' + checkout_path, 'docs', 'requirements.txt', ) root_requirements = os.path.join( - checkout_path, 'requirements.txt' + checkout_path, 'requirements.txt', ) paths = { os.path.join(checkout_path, 'docs'): True, @@ -1257,7 +1294,7 @@ class TestPythonEnvironment(TestCase): '--cache-dir', mock.ANY, # cache path '-r', - 'requirements_file' + 'requirements_file', ] # One requirements file on the docs/ dir @@ -1344,7 +1381,7 @@ class TestPythonEnvironment(TestCase): self.build_env_mock.run.assert_has_calls([ mock.call(*args_conda, cwd=mock.ANY), - mock.call(*args_pip, bin_path=mock.ANY, cwd=mock.ANY) + mock.call(*args_pip, bin_path=mock.ANY, cwd=mock.ANY), ]) @patch('readthedocs.projects.models.Project.checkout_path') @@ -1385,7 +1422,7 @@ class TestPythonEnvironment(TestCase): self.build_env_mock.run.assert_has_calls([ mock.call(*args_conda, cwd=mock.ANY), - mock.call(*args_pip, bin_path=mock.ANY, cwd=mock.ANY) + mock.call(*args_pip, bin_path=mock.ANY, cwd=mock.ANY), ]) @patch('readthedocs.projects.models.Project.checkout_path') diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index a8e42afdc..d3e5ba749 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -17,7 +17,7 @@ from readthedocs.rtd_tests.base import RequestFactoryTestMixin @override_settings( - USE_SUBDOMAIN=False, PUBLIC_DOMAIN='public.readthedocs.org', DEBUG=False + USE_SUBDOMAIN=False, PUBLIC_DOMAIN='public.readthedocs.org', DEBUG=False, ) class BaseDocServing(RequestFactoryTestMixin, TestCase): @@ -46,7 +46,7 @@ class TestPrivateDocs(BaseDocServing): serve_mock.assert_called_with( request, 'en/latest/usage.html', - settings.SITE_ROOT + '/private_web_root/private' + settings.SITE_ROOT + '/private_web_root/private', ) @override_settings(PYTHON_MEDIA=False) @@ -56,7 +56,7 @@ class TestPrivateDocs(BaseDocServing): r = _serve_symlink_docs(request, project=self.private, filename='/en/latest/usage.html', privacy_level='private') self.assertEqual(r.status_code, 200) self.assertEqual( - r._headers['x-accel-redirect'][1], '/private_web_root/private/en/latest/usage.html' + r._headers['x-accel-redirect'][1], '/private_web_root/private/en/latest/usage.html', ) @override_settings(PYTHON_MEDIA=False) @@ -66,7 +66,7 @@ class TestPrivateDocs(BaseDocServing): r = _serve_symlink_docs(request, project=self.private, filename='/en/latest/úñíčódé.html', privacy_level='private') self.assertEqual(r.status_code, 200) self.assertEqual( - r._headers['x-accel-redirect'][1], '/private_web_root/private/en/latest/%C3%BA%C3%B1%C3%AD%C4%8D%C3%B3d%C3%A9.html' + r._headers['x-accel-redirect'][1], '/private_web_root/private/en/latest/%C3%BA%C3%B1%C3%AD%C4%8D%C3%B3d%C3%A9.html', ) @override_settings(PYTHON_MEDIA=False) @@ -112,7 +112,7 @@ class TestPublicDocs(BaseDocServing): serve_mock.assert_called_with( request, 'en/latest/usage.html', - settings.SITE_ROOT + '/public_web_root/public' + settings.SITE_ROOT + '/public_web_root/public', ) @override_settings(PYTHON_MEDIA=False) @@ -122,7 +122,7 @@ class TestPublicDocs(BaseDocServing): r = _serve_symlink_docs(request, project=self.public, filename='/en/latest/usage.html', privacy_level='public') self.assertEqual(r.status_code, 200) self.assertEqual( - r._headers['x-accel-redirect'][1], '/public_web_root/public/en/latest/usage.html' + r._headers['x-accel-redirect'][1], '/public_web_root/public/en/latest/usage.html', ) @override_settings(PYTHON_MEDIA=False) diff --git a/readthedocs/rtd_tests/tests/test_domains.py b/readthedocs/rtd_tests/tests/test_domains.py index cfa13b0c4..8c9610ac4 100644 --- a/readthedocs/rtd_tests/tests/test_domains.py +++ b/readthedocs/rtd_tests/tests/test_domains.py @@ -71,32 +71,44 @@ class FormTests(TestCase): def test_https(self): """Make sure https is an admin-only attribute.""" - form = DomainForm({'domain': 'example.com', 'canonical': True}, - project=self.project) + form = DomainForm( + {'domain': 'example.com', 'canonical': True}, + project=self.project, + ) self.assertTrue(form.is_valid()) domain = form.save() self.assertFalse(domain.https) - form = DomainForm({'domain': 'example.com', 'canonical': True, - 'https': True}, - project=self.project) + form = DomainForm( + { + 'domain': 'example.com', 'canonical': True, + 'https': True, + }, + project=self.project, + ) self.assertFalse(form.is_valid()) def test_canonical_change(self): """Make sure canonical can be properly changed.""" - form = DomainForm({'domain': 'example.com', 'canonical': True}, - project=self.project) + form = DomainForm( + {'domain': 'example.com', 'canonical': True}, + project=self.project, + ) self.assertTrue(form.is_valid()) domain = form.save() self.assertEqual(domain.domain, 'example.com') - form = DomainForm({'domain': 'example2.com', 'canonical': True}, - project=self.project) + form = DomainForm( + {'domain': 'example2.com', 'canonical': True}, + project=self.project, + ) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['canonical'][0], 'Only 1 Domain can be canonical at a time.') - form = DomainForm({'domain': 'example2.com', 'canonical': True}, - project=self.project, - instance=domain) + form = DomainForm( + {'domain': 'example2.com', 'canonical': True}, + project=self.project, + instance=domain, + ) self.assertTrue(form.is_valid()) domain = form.save() self.assertEqual(domain.domain, 'example2.com') diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py index 1d452cf82..74ddbcf8d 100644 --- a/readthedocs/rtd_tests/tests/test_extend.py +++ b/readthedocs/rtd_tests/tests/test_extend.py @@ -63,10 +63,12 @@ class ExtendTests(TestCase): override_class = get_override_class(Foo, Foo._default_class) self.assertEqual(override_class, NewFoo) - @override_settings(FOO_OVERRIDE_CLASS=None, - CLASS_OVERRIDES={ - EXTEND_PATH: EXTEND_OVERRIDE_PATH, - }) + @override_settings( + FOO_OVERRIDE_CLASS=None, + CLASS_OVERRIDES={ + EXTEND_PATH: EXTEND_OVERRIDE_PATH, + }, + ) def test_with_advanced_override(self): """Test class with override using `CLASS_OVERRIDES`""" class Foo(SettingsOverrideObject): @@ -81,10 +83,12 @@ class ExtendTests(TestCase): override_class = get_override_class(Foo, Foo._default_class) self.assertEqual(override_class, NewFoo) - @override_settings(FOO_OVERRIDE_CLASS=None, - CLASS_OVERRIDES={ - EXTEND_PATH: EXTEND_OVERRIDE_PATH, - }) + @override_settings( + FOO_OVERRIDE_CLASS=None, + CLASS_OVERRIDES={ + EXTEND_PATH: EXTEND_OVERRIDE_PATH, + }, + ) def test_with_advanced_override_only(self): """Test class with no `override_setting`""" class Foo(SettingsOverrideObject): diff --git a/readthedocs/rtd_tests/tests/test_gold.py b/readthedocs/rtd_tests/tests/test_gold.py index 51d891eef..b52ec2938 100644 --- a/readthedocs/rtd_tests/tests/test_gold.py +++ b/readthedocs/rtd_tests/tests/test_gold.py @@ -34,7 +34,7 @@ class GoldViewTests(TestCase): self.assertEqual(resp.status_code, 302) resp = self.client.post(reverse('gold_projects'), data={'project': self.project2.slug}) self.assertFormError( - resp, form='form', field=None, errors='You already have the max number of supported projects.' + resp, form='form', field=None, errors='You already have the max number of supported projects.', ) self.assertEqual(resp.status_code, 200) self.assertEqual(self.golduser.projects.count(), 1) diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py index e7be65883..54e6922a4 100644 --- a/readthedocs/rtd_tests/tests/test_integrations.py +++ b/readthedocs/rtd_tests/tests/test_integrations.py @@ -24,23 +24,27 @@ class HttpExchangeTests(TestCase): client = APIClient() client.login(username='super', password='test') project = fixture.get(Project, main_language_project=None) - integration = fixture.get(Integration, project=project, - integration_type=Integration.GITHUB_WEBHOOK, - provider_data='') + integration = fixture.get( + Integration, project=project, + integration_type=Integration.GITHUB_WEBHOOK, + provider_data='', + ) resp = client.post( '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'exchange_json'}, - format='json' + format='json', ) exchange = HttpExchange.objects.get(integrations=integration) self.assertEqual( exchange.request_body, - '{"ref": "exchange_json"}' + '{"ref": "exchange_json"}', ) self.assertEqual( exchange.request_headers, - {'Content-Type': 'application/json; charset=None', - 'Cookie': ''} + { + 'Content-Type': 'application/json; charset=None', + 'Cookie': '', + }, ) self.assertEqual( exchange.response_body, @@ -49,17 +53,21 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.response_headers, - {'Allow': 'POST, OPTIONS', - 'Content-Type': 'text/html; charset=utf-8'} + { + 'Allow': 'POST, OPTIONS', + 'Content-Type': 'text/html; charset=utf-8', + }, ) def test_exchange_form_request_body(self): client = APIClient() client.login(username='super', password='test') project = fixture.get(Project, main_language_project=None) - integration = fixture.get(Integration, project=project, - integration_type=Integration.GITHUB_WEBHOOK, - provider_data='') + integration = fixture.get( + Integration, project=project, + integration_type=Integration.GITHUB_WEBHOOK, + provider_data='', + ) resp = client.post( '/api/v2/webhook/github/{}/'.format(project.slug), 'payload=%7B%22ref%22%3A+%22exchange_form%22%7D', @@ -68,12 +76,14 @@ class HttpExchangeTests(TestCase): exchange = HttpExchange.objects.get(integrations=integration) self.assertEqual( exchange.request_body, - '{"ref": "exchange_form"}' + '{"ref": "exchange_form"}', ) self.assertEqual( exchange.request_headers, - {'Content-Type': 'application/x-www-form-urlencoded', - 'Cookie': ''} + { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': '', + }, ) self.assertEqual( exchange.response_body, @@ -82,55 +92,61 @@ class HttpExchangeTests(TestCase): ) self.assertEqual( exchange.response_headers, - {'Allow': 'POST, OPTIONS', - 'Content-Type': 'text/html; charset=utf-8'} + { + 'Allow': 'POST, OPTIONS', + 'Content-Type': 'text/html; charset=utf-8', + }, ) def test_extraneous_exchanges_deleted_in_correct_order(self): client = APIClient() client.login(username='super', password='test') project = fixture.get(Project, main_language_project=None) - integration = fixture.get(Integration, project=project, - integration_type=Integration.GITHUB_WEBHOOK, - provider_data='') + integration = fixture.get( + Integration, project=project, + integration_type=Integration.GITHUB_WEBHOOK, + provider_data='', + ) self.assertEqual( HttpExchange.objects.filter(integrations=integration).count(), - 0 + 0, ) for _ in range(10): resp = client.post( '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'deleted'}, - format='json' + format='json', ) for _ in range(10): resp = client.post( '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'preserved'}, - format='json' + format='json', ) self.assertEqual( HttpExchange.objects.filter(integrations=integration).count(), - 10 + 10, ) self.assertEqual( HttpExchange.objects.filter( integrations=integration, request_body='{"ref": "preserved"}', ).count(), - 10 + 10, ) def test_request_headers_are_removed(self): client = APIClient() client.login(username='super', password='test') project = fixture.get(Project, main_language_project=None) - integration = fixture.get(Integration, project=project, - integration_type=Integration.GITHUB_WEBHOOK, - provider_data='') + integration = fixture.get( + Integration, project=project, + integration_type=Integration.GITHUB_WEBHOOK, + provider_data='', + ) resp = client.post( '/api/v2/webhook/github/{}/'.format(project.slug), {'ref': 'exchange_json'}, @@ -142,9 +158,11 @@ class HttpExchangeTests(TestCase): exchange = HttpExchange.objects.get(integrations=integration) self.assertEqual( exchange.request_headers, - {'Content-Type': 'application/json; charset=None', - 'Cookie': '', - 'X-Foo': 'bar'} + { + 'Content-Type': 'application/json; charset=None', + 'Cookie': '', + 'X-Foo': 'bar', + }, ) @@ -154,7 +172,7 @@ class IntegrationModelTests(TestCase): project = fixture.get(Project, main_language_project=None) integration = Integration.objects.create( project=project, - integration_type=Integration.GITHUB_WEBHOOK + integration_type=Integration.GITHUB_WEBHOOK, ) integration = Integration.objects.get(pk=integration.pk) self.assertIsInstance(integration, GitHubWebhook) @@ -163,7 +181,7 @@ class IntegrationModelTests(TestCase): project = fixture.get(Project, main_language_project=None) integration = Integration.objects.create( project=project, - integration_type=Integration.GITHUB_WEBHOOK + integration_type=Integration.GITHUB_WEBHOOK, ) integration = Integration.objects.subclass(integration) self.assertIsInstance(integration, GitHubWebhook) diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py index 093957f71..4a93274da 100644 --- a/readthedocs/rtd_tests/tests/test_middleware.py +++ b/readthedocs/rtd_tests/tests/test_middleware.py @@ -147,7 +147,7 @@ class TestCORSMiddleware(TestCase): self.project = get( Project, slug='pip', users=[self.owner], privacy_level='public', - mail_language_project=None + mail_language_project=None, ) self.subproject = get( Project, @@ -158,7 +158,7 @@ class TestCORSMiddleware(TestCase): self.relationship = get( ProjectRelationship, parent=self.project, - child=self.subproject + child=self.subproject, ) self.domain = get(Domain, domain='my.valid.domain', project=self.project) diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index cd3df63ec..1ead82e53 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -48,21 +48,33 @@ class NotificationTests(TestCase): req = mock.MagicMock() notify = TestNotification(context_object=build, request=req) - self.assertEqual(notify.get_template_names('email'), - ['builds/notifications/foo_email.html']) - self.assertEqual(notify.get_template_names('site'), - ['builds/notifications/foo_site.html']) - self.assertEqual(notify.get_subject(), - 'This is {}'.format(build.id)) - self.assertEqual(notify.get_context_data(), - {'foo': build, - 'production_uri': 'https://readthedocs.org', - 'request': req}) + self.assertEqual( + notify.get_template_names('email'), + ['builds/notifications/foo_email.html'], + ) + self.assertEqual( + notify.get_template_names('site'), + ['builds/notifications/foo_site.html'], + ) + self.assertEqual( + notify.get_subject(), + 'This is {}'.format(build.id), + ) + self.assertEqual( + notify.get_context_data(), + { + 'foo': build, + 'production_uri': 'https://readthedocs.org', + 'request': req, + }, + ) notify.render('site') render_to_string.assert_has_calls([ - mock.call(context=mock.ANY, - template_name=['builds/notifications/foo_site.html']) + mock.call( + context=mock.ANY, + template_name=['builds/notifications/foo_site.html'], + ), ]) @@ -95,7 +107,7 @@ class NotificationBackendTests(TestCase): subject='This is {}'.format(build.id), template_html='core/email/common.html', recipient=user.email, - ) + ), ]) def test_message_backend(self, render_to_string): diff --git a/readthedocs/rtd_tests/tests/test_oauth.py b/readthedocs/rtd_tests/tests/test_oauth.py index 87d9439a1..1ef675cc4 100644 --- a/readthedocs/rtd_tests/tests/test_oauth.py +++ b/readthedocs/rtd_tests/tests/test_oauth.py @@ -39,7 +39,8 @@ class GitHubOAuthTests(TestCase): 'clone_url': 'https://github.com/testuser/testrepo.git', } repo = self.service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) self.assertIsInstance(repo, RemoteRepository) self.assertEqual(repo.name, 'testrepo') self.assertEqual(repo.full_name, 'testuser/testrepo') @@ -51,9 +52,11 @@ class GitHubOAuthTests(TestCase): self.assertIn(self.user, repo.users.all()) self.assertEqual(repo.organization, self.org) self.assertEqual( - repo.clone_url, 'https://github.com/testuser/testrepo.git') + repo.clone_url, 'https://github.com/testuser/testrepo.git', + ) self.assertEqual( - repo.ssh_url, 'ssh://git@github.com:testuser/testrepo.git') + repo.ssh_url, 'ssh://git@github.com:testuser/testrepo.git', + ) self.assertEqual(repo.html_url, 'https://github.com/testuser/testrepo') def test_make_project_fail(self): @@ -68,7 +71,8 @@ class GitHubOAuthTests(TestCase): 'clone_url': '', } github_project = self.service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) self.assertIsNone(github_project) def test_make_organization(self): @@ -105,29 +109,35 @@ class GitHubOAuthTests(TestCase): } github_project = self.service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) user2 = User.objects.get(pk=2) service = GitHubService(user=user2, account=None) github_project_2 = service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) self.assertIsInstance(github_project, RemoteRepository) self.assertIsInstance(github_project_2, RemoteRepository) self.assertNotEqual(github_project_2, github_project) github_project_3 = self.service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) github_project_4 = service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) self.assertIsInstance(github_project_3, RemoteRepository) self.assertIsInstance(github_project_4, RemoteRepository) self.assertEqual(github_project, github_project_3) self.assertEqual(github_project_2, github_project_4) github_project_5 = self.service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) github_project_6 = service.create_repository( - repo_json, organization=self.org, privacy=self.privacy) + repo_json, organization=self.org, privacy=self.privacy, + ) self.assertEqual(github_project, github_project_5) self.assertEqual(github_project_2, github_project_6) @@ -261,7 +271,8 @@ class BitbucketOAuthTests(TestCase): def test_make_project_pass(self): repo = self.service.create_repository( self.repo_response_data, organization=self.org, - privacy=self.privacy) + privacy=self.privacy, + ) self.assertIsInstance(repo, RemoteRepository) self.assertEqual(repo.name, 'tutorials.bitbucket.org') self.assertEqual(repo.full_name, 'tutorials/tutorials.bitbucket.org') @@ -269,24 +280,30 @@ class BitbucketOAuthTests(TestCase): self.assertEqual( repo.avatar_url, ( 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2012/Nov/28/' - 'tutorials.bitbucket.org-logo-1456883302-9_avatar.png')) + 'tutorials.bitbucket.org-logo-1456883302-9_avatar.png' + ), + ) self.assertIn(self.user, repo.users.all()) self.assertEqual(repo.organization, self.org) self.assertEqual( repo.clone_url, - 'https://bitbucket.org/tutorials/tutorials.bitbucket.org') + 'https://bitbucket.org/tutorials/tutorials.bitbucket.org', + ) self.assertEqual( repo.ssh_url, - 'ssh://hg@bitbucket.org/tutorials/tutorials.bitbucket.org') + 'ssh://hg@bitbucket.org/tutorials/tutorials.bitbucket.org', + ) self.assertEqual( repo.html_url, - 'https://bitbucket.org/tutorials/tutorials.bitbucket.org') + 'https://bitbucket.org/tutorials/tutorials.bitbucket.org', + ) def test_make_project_fail(self): data = self.repo_response_data.copy() data['is_private'] = True repo = self.service.create_repository( - data, organization=self.org, privacy=self.privacy) + data, organization=self.org, privacy=self.privacy, + ) self.assertIsNone(repo) @override_settings(DEFAULT_PRIVACY_LEVEL='private') @@ -307,7 +324,9 @@ class BitbucketOAuthTests(TestCase): self.assertEqual( org.avatar_url, ( 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2014/Sep/24/' - 'teamsinspace-avatar-3731530358-7_avatar.png')) + 'teamsinspace-avatar-3731530358-7_avatar.png' + ), + ) self.assertEqual(org.url, 'https://bitbucket.org/teamsinspace') def test_import_with_no_token(self): @@ -430,7 +449,8 @@ class GitLabOAuthTests(TestCase): m.return_value = True repo = self.service.create_repository( self.repo_response_data, organization=self.org, - privacy=self.privacy) + privacy=self.privacy, + ) self.assertIsInstance(repo, RemoteRepository) self.assertEqual(repo.name, 'testrepo') self.assertEqual(repo.full_name, 'testorga / testrepo') @@ -453,13 +473,15 @@ class GitLabOAuthTests(TestCase): def test_make_private_project_fail(self): repo = self.service.create_repository( self.get_private_repo_data(), organization=self.org, - privacy=self.privacy) + privacy=self.privacy, + ) self.assertIsNone(repo) def test_make_private_project_success(self): repo = self.service.create_repository( self.get_private_repo_data(), organization=self.org, - privacy=constants.PRIVATE) + privacy=constants.PRIVATE, + ) self.assertIsInstance(repo, RemoteRepository) self.assertTrue(repo.private, True) diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py index cc8c1009d..4dd40865e 100644 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py @@ -17,15 +17,19 @@ log = logging.getLogger(__name__) class BasePostCommitTest(TestCase): def _setup(self): self.rtfd = get( - Project, repo='https://github.com/rtfd/readthedocs.org', slug='read-the-docs') + Project, repo='https://github.com/rtfd/readthedocs.org', slug='read-the-docs', + ) self.rtfd_not_ok = get( - Version, project=self.rtfd, slug='not_ok', identifier='not_ok', active=False) + Version, project=self.rtfd, slug='not_ok', identifier='not_ok', active=False, + ) self.rtfd_awesome = get( - Version, project=self.rtfd, slug='awesome', identifier='awesome', active=True) + Version, project=self.rtfd, slug='awesome', identifier='awesome', active=True, + ) self.pip = get(Project, repo='https://bitbucket.org/pip/pip', repo_type='hg') self.pip_not_ok = get( - Version, project=self.pip, slug='not_ok', identifier='not_ok', active=False) + Version, project=self.pip, slug='not_ok', identifier='not_ok', active=False, + ) self.sphinx = get(Project, repo='https://bitbucket.org/sphinx/sphinx', repo_type='git') self.mocks = [mock.patch('readthedocs.core.views.hooks.trigger_build')] @@ -76,7 +80,7 @@ class GitLabWebHookTest(BasePostCommitTest): 'homepage':'http://example.com/mike/diaspora', 'url':'git@github.com/rtfd/readthedocs.org.git', 'ssh_url':'git@github.com/rtfd/readthedocs.org.git', - 'http_url':'http://github.com/rtfd/readthedocs.org.git' + 'http_url':'http://github.com/rtfd/readthedocs.org.git', }, 'repository':{ 'name': 'Diaspora', @@ -85,7 +89,7 @@ class GitLabWebHookTest(BasePostCommitTest): 'homepage': 'http://github.com/rtfd/readthedocs.org', 'git_http_url': 'http://github.com/rtfd/readthedocs.org.git', 'git_ssh_url': 'git@github.com:rtfd/readthedocs.org.git', - 'visibility_level': 0 + 'visibility_level': 0, }, 'commits': [ { @@ -95,11 +99,11 @@ class GitLabWebHookTest(BasePostCommitTest): 'url': 'http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327', 'author': { 'name': 'Jordi Mallach', - 'email': 'jordi@softcatala.org' + 'email': 'jordi@softcatala.org', }, 'added': ['CHANGELOG'], 'modified': ['app/controller/application.rb'], - 'removed': [] + 'removed': [], }, { 'id': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', @@ -108,30 +112,36 @@ class GitLabWebHookTest(BasePostCommitTest): 'url': 'http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7', 'author': { 'name': 'GitLab dev user', - 'email': 'gitlabdev@dv6700.(none)' + 'email': 'gitlabdev@dv6700.(none)', }, 'added': ['CHANGELOG'], 'modified': ['app/controller/application.rb'], - 'removed': [] - } + 'removed': [], + }, ], - 'total_commits_count': 4 + 'total_commits_count': 4, } def test_gitlab_post_commit_hook_builds_branch_docs_if_it_should(self): """GitLab webhook should only build active versions.""" - r = self.client.post('/gitlab/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/gitlab/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') self.payload['ref'] = 'refs/heads/not_ok' - r = self.client.post('/gitlab/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/gitlab/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]') self.payload['ref'] = 'refs/heads/unknown' - r = self.client.post('/gitlab/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/gitlab/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) No known branches were pushed to.') def test_gitlab_post_commit_knows_default_branches(self): @@ -145,8 +155,10 @@ class GitLabWebHookTest(BasePostCommitTest): rtd.save() self.payload['ref'] = 'refs/heads/master' - r = self.client.post('/gitlab/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/gitlab/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]') rtd.default_branch = old_default @@ -160,7 +172,7 @@ class GitLabWebHookTest(BasePostCommitTest): self.payload['project']['http_url'] = '' r = self.client.post( '/gitlab/', data=json.dumps(self.payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 404) @@ -177,7 +189,7 @@ class GitLabWebHookTest(BasePostCommitTest): r = self.client.post( '/gitlab/', data=json.dumps(payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 403) @@ -198,27 +210,31 @@ class GitHubWebHookTest(BasePostCommitTest): 'author': { 'email': 'eric@ericholscher.com', 'name': 'Eric Holscher', - 'username': 'ericholscher' + 'username': 'ericholscher', }, 'distinct': False, 'id': '11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a', 'message': 'Fix it on the front list as well.', 'modified': [ - 'readthedocs/templates/core/project_list_detailed.html' + 'readthedocs/templates/core/project_list_detailed.html', ], 'removed': [], 'timestamp': '2011-09-12T19:38:55-07:00', - 'url': ('https://github.com/wraithan/readthedocs.org/' - 'commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a') + 'url': ( + 'https://github.com/wraithan/readthedocs.org/' + 'commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a' + ), }, ], - 'compare': ('https://github.com/wraithan/readthedocs.org/compare/' - '5b4e453...5ad7573'), + 'compare': ( + 'https://github.com/wraithan/readthedocs.org/compare/' + '5b4e453...5ad7573' + ), 'created': False, 'deleted': False, 'forced': False, 'pusher': { - 'name': 'none' + 'name': 'none', }, 'ref': 'refs/heads/awesome', 'repository': { @@ -235,27 +251,31 @@ class GitHubWebHookTest(BasePostCommitTest): 'open_issues': 0, 'owner': { 'email': 'XWraithanX@gmail.com', - 'name': 'wraithan' + 'name': 'wraithan', }, 'private': False, 'pushed_at': '2011/09/12 22:33:34 -0700', 'size': 140, 'url': 'https://github.com/rtfd/readthedocs.org', 'ssh_url': 'git@github.com:rtfd/readthedocs.org.git', - 'watchers': 1 + 'watchers': 1, - } + }, } def test_post_types(self): """Ensure various POST formats.""" - r = self.client.post('/github/', - data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/github/', + data=json.dumps(self.payload), + content_type='application/json', + ) self.assertEqual(r.status_code, 200) - r = self.client.post('/github/', - data=urlencode({'payload': json.dumps(self.payload)}), - content_type='application/x-www-form-urlencoded') + r = self.client.post( + '/github/', + data=urlencode({'payload': json.dumps(self.payload)}), + content_type='application/x-www-form-urlencoded', + ) self.assertEqual(r.status_code, 200) def test_github_upper_case_repo(self): @@ -267,8 +287,10 @@ class GitHubWebHookTest(BasePostCommitTest): """ payload = self.payload.copy() payload['repository']['url'] = payload['repository']['url'].upper() - r = self.client.post('/github/', data=json.dumps(payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: HTTPS://GITHUB.COM/RTFD/READTHEDOCS.ORG [awesome]') self.payload['ref'] = 'refs/heads/not_ok' @@ -280,8 +302,10 @@ class GitHubWebHookTest(BasePostCommitTest): """ payload = self.payload.copy() del payload['ref'] - r = self.client.post('/github/', data=json.dumps(payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(payload), + content_type='application/json', + ) self.assertEqual(r.status_code, 400) def test_github_request_empty_url(self): @@ -293,7 +317,7 @@ class GitHubWebHookTest(BasePostCommitTest): self.payload['repository']['ssh_url'] = '' r = self.client.post( '/github/', data=json.dumps(self.payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 403) @@ -308,8 +332,10 @@ class GitHubWebHookTest(BasePostCommitTest): self.rtfd.repo = 'git@github.com:rtfd/readthedocs.org' self.rtfd.save() payload = self.payload.copy() - r = self.client.post('/github/', data=json.dumps(payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') def test_github_post_commit_hook_builds_branch_docs_if_it_should(self): @@ -318,18 +344,24 @@ class GitHubWebHookTest(BasePostCommitTest): versions that are set to be built if the branch they refer to is updated. Otherwise it is no op. """ - r = self.client.post('/github/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') self.payload['ref'] = 'refs/heads/not_ok' - r = self.client.post('/github/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]') self.payload['ref'] = 'refs/heads/unknown' - r = self.client.post('/github/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) No known branches were pushed to.') def test_github_post_commit_knows_default_branches(self): @@ -343,8 +375,10 @@ class GitHubWebHookTest(BasePostCommitTest): rtd.save() self.payload['ref'] = 'refs/heads/master' - r = self.client.post('/github/', data=json.dumps(self.payload), - content_type='application/json') + r = self.client.post( + '/github/', data=json.dumps(self.payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]') rtd.default_branch = old_default @@ -363,7 +397,7 @@ class GitHubWebHookTest(BasePostCommitTest): r = self.client.post( '/github/', data=json.dumps(payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 403) @@ -380,8 +414,10 @@ class CorePostCommitTest(BasePostCommitTest): rtd.save() r = self.client.post('/build/%s' % rtd.pk, {'version_slug': 'master'}) self.assertEqual(r.status_code, 302) - self.assertEqual(r._headers['location'][1], - '/projects/read-the-docs/builds/') + self.assertEqual( + r._headers['location'][1], + '/projects/read-the-docs/builds/', + ) def test_hook_state_tracking(self): rtd = Project.objects.get(slug='read-the-docs') @@ -411,21 +447,21 @@ class BitBucketWebHookTest(BasePostCommitTest): 'files': [ { 'file': 'somefile.py', - 'type': 'modified' - } + 'type': 'modified', + }, ], 'message': 'Added some feature things', 'node': 'd14d26a93fd2', 'parents': [ - '1b458191f31a' + '1b458191f31a', ], 'raw_author': 'Marcus Bertrand ', 'raw_node': 'd14d26a93fd28d3166fa81c0cd3b6f339bb95bfe', 'revision': 3, 'size': -1, 'timestamp': '2012-05-30 06:07:03', - 'utctimestamp': '2012-05-30 04:07:03+00:00' - } + 'utctimestamp': '2012-05-30 04:07:03+00:00', + }, ], 'repository': { 'absolute_url': '/pip/pip/', @@ -435,9 +471,9 @@ class BitBucketWebHookTest(BasePostCommitTest): 'owner': 'marcus', 'scm': 'hg', 'slug': 'project-x', - 'website': '' + 'website': '', }, - 'user': 'marcus' + 'user': 'marcus', } self.git_payload = { @@ -449,21 +485,21 @@ class BitBucketWebHookTest(BasePostCommitTest): 'files': [ { 'file': 'somefile.py', - 'type': 'modified' - } + 'type': 'modified', + }, ], 'message': 'Added some more things to somefile.py\n', 'node': '620ade18607a', 'parents': [ - '702c70160afc' + '702c70160afc', ], 'raw_author': 'Marcus Bertrand ', 'raw_node': '620ade18607ac42d872b568bb92acaa9a28620e9', 'revision': None, 'size': -1, 'timestamp': '2012-05-30 05:58:56', - 'utctimestamp': '2012-05-30 03:58:56+00:00' - } + 'utctimestamp': '2012-05-30 03:58:56+00:00', + }, ], 'repository': { 'absolute_url': '/sphinx/sphinx/', @@ -473,53 +509,69 @@ class BitBucketWebHookTest(BasePostCommitTest): 'owner': 'marcus', 'scm': 'git', 'slug': 'project-x', - 'website': 'https://atlassian.com/' + 'website': 'https://atlassian.com/', }, - 'user': 'marcus' + 'user': 'marcus', } def test_post_types(self): """Ensure various POST formats.""" - r = self.client.post('/bitbucket/', - data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', + data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertEqual(r.status_code, 200) - r = self.client.post('/bitbucket/', - data=urlencode({'payload': json.dumps(self.hg_payload)}), - content_type='application/x-www-form-urlencoded') + r = self.client.post( + '/bitbucket/', + data=urlencode({'payload': json.dumps(self.hg_payload)}), + content_type='application/x-www-form-urlencoded', + ) self.assertEqual(r.status_code, 200) def test_bitbucket_post_commit(self): - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]') - r = self.client.post('/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.git_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: bitbucket.org/sphinx/sphinx [latest]') def test_bitbucket_post_commit_empty_commit_list(self): self.hg_payload['commits'] = [] self.git_payload['commits'] = [] - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, 'Commit/branch not found', status_code=404) - r = self.client.post('/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.git_payload), + content_type='application/json', + ) self.assertContains(r, 'Commit/branch not found', status_code=404) def test_bitbucket_post_commit_non_existent_url(self): self.hg_payload['repository']['absolute_url'] = '/invalid/repository' self.git_payload['repository']['absolute_url'] = '/invalid/repository' - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, 'Project match not found', status_code=404) - r = self.client.post('/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.git_payload), + content_type='application/json', + ) self.assertContains(r, 'Project match not found', status_code=404) @@ -529,22 +581,28 @@ class BitBucketWebHookTest(BasePostCommitTest): versions that are set to be built if the branch they refer to is updated. Otherwise it is no op. """ - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]') self.hg_payload['commits'] = [{ "branch": "not_ok", }] - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Not Building: bitbucket.org/pip/pip [not_ok]') self.hg_payload['commits'] = [{ "branch": "unknown", }] - r = self.client.post('/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.hg_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) No known branches were pushed to.') def test_bitbucket_default_branch(self): @@ -558,11 +616,13 @@ class BitBucketWebHookTest(BasePostCommitTest): 'branch': 'integration', }] self.git_payload['repository'] = { - 'absolute_url': '/test/project/' + 'absolute_url': '/test/project/', } - r = self.client.post('/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json') + r = self.client.post( + '/bitbucket/', data=json.dumps(self.git_payload), + content_type='application/json', + ) self.assertContains(r, '(URL Build) Build Started: bitbucket.org/test/project [latest]') def test_bitbucket_request_empty_url(self): @@ -573,7 +633,7 @@ class BitBucketWebHookTest(BasePostCommitTest): self.git_payload['repository']['absolute_url'] = '' r = self.client.post( '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 400) @@ -590,6 +650,6 @@ class BitBucketWebHookTest(BasePostCommitTest): r = self.client.post( '/bitbucket/', data=json.dumps(payload), - content_type='application/json' + content_type='application/json', ) self.assertEqual(r.status_code, 403) diff --git a/readthedocs/rtd_tests/tests/test_privacy.py b/readthedocs/rtd_tests/tests/test_privacy.py index e273eafc4..2f55a0fd9 100644 --- a/readthedocs/rtd_tests/tests/test_privacy.py +++ b/readthedocs/rtd_tests/tests/test_privacy.py @@ -30,8 +30,10 @@ class PrivacyTests(TestCase): tasks.update_docs_task.delay = mock.Mock() - def _create_kong(self, privacy_level='private', - version_privacy_level='private'): + def _create_kong( + self, privacy_level='private', + version_privacy_level='private', + ): self.client.login(username='eric', password='test') log.info( 'Making kong with privacy: %s and version privacy: %s', @@ -40,17 +42,20 @@ class PrivacyTests(TestCase): ) # Create project via project form, simulate import wizard without magic form = UpdateProjectForm( - data={'repo_type': 'git', - 'repo': 'https://github.com/ericholscher/django-kong', - 'name': 'Django Kong', - 'language': 'en', - 'default_branch': '', - 'project_url': 'http://django-kong.rtfd.org', - 'default_version': LATEST, - 'python_interpreter': 'python', - 'description': 'OOHHH AH AH AH KONG SMASH', - 'documentation_type': 'sphinx'}, - user=User.objects.get(username='eric')) + data={ + 'repo_type': 'git', + 'repo': 'https://github.com/ericholscher/django-kong', + 'name': 'Django Kong', + 'language': 'en', + 'default_branch': '', + 'project_url': 'http://django-kong.rtfd.org', + 'default_version': LATEST, + 'python_interpreter': 'python', + 'description': 'OOHHH AH AH AH KONG SMASH', + 'documentation_type': 'sphinx', + }, + user=User.objects.get(username='eric'), + ) proj = form.save() # Update these directly, no form has all the fields we need proj.privacy_level = privacy_level @@ -130,8 +135,10 @@ class PrivacyTests(TestCase): kong = self._create_kong('public', 'private') self.client.login(username='eric', password='test') - Version.objects.create(project=kong, identifier='test id', - verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True) + Version.objects.create( + project=kong, identifier='test id', + verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True, + ) self.assertEqual(Version.objects.count(), 2) self.assertEqual(Version.objects.get(slug='test-slug').privacy_level, 'private') r = self.client.get('/projects/django-kong/') @@ -150,9 +157,11 @@ class PrivacyTests(TestCase): kong = self._create_kong('public', 'public') self.client.login(username='eric', password='test') - Version.objects.create(project=kong, identifier='test id', - verbose_name='test verbose', slug='test-slug', - active=True, built=True) + Version.objects.create( + project=kong, identifier='test id', + verbose_name='test verbose', slug='test-slug', + active=True, built=True, + ) self.assertEqual(Version.objects.count(), 2) self.assertEqual(Version.objects.all()[0].privacy_level, 'public') r = self.client.get('/projects/django-kong/') @@ -166,22 +175,30 @@ class PrivacyTests(TestCase): def test_public_repo_api(self): self._create_kong('public', 'public') self.client.login(username='eric', password='test') - resp = self.client.get('http://testserver/api/v1/project/django-kong/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) - resp = self.client.get('http://testserver/api/v1/project/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) self.client.login(username='tester', password='test') - resp = self.client.get('http://testserver/api/v1/project/django-kong/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) - resp = self.client.get('http://testserver/api/v1/project/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) @@ -189,21 +206,29 @@ class PrivacyTests(TestCase): def test_private_repo_api(self): self._create_kong('private', 'private') self.client.login(username='eric', password='test') - resp = self.client.get('http://testserver/api/v1/project/django-kong/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) - resp = self.client.get('http://testserver/api/v1/project/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 1) self.client.login(username='tester', password='test') - resp = self.client.get('http://testserver/api/v1/project/django-kong/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/django-kong/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 404) - resp = self.client.get('http://testserver/api/v1/project/', - data={'format': 'json'}) + resp = self.client.get( + 'http://testserver/api/v1/project/', + data={'format': 'json'}, + ) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['meta']['total_count'], 0) @@ -212,11 +237,17 @@ class PrivacyTests(TestCase): kong = self._create_kong('public', 'private') self.client.login(username='eric', password='test') - Version.objects.create(project=kong, identifier='test id', - verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True) - self.client.post('/dashboard/django-kong/versions/', - {'version-test-slug': 'on', - 'privacy-test-slug': 'private'}) + Version.objects.create( + project=kong, identifier='test id', + verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True, + ) + self.client.post( + '/dashboard/django-kong/versions/', + { + 'version-test-slug': 'on', + 'privacy-test-slug': 'private', + }, + ) r = self.client.get('/docs/django-kong/en/test-slug/') self.client.login(username='eric', password='test') self.assertEqual(r.status_code, 404) @@ -350,8 +381,10 @@ class PrivacyTests(TestCase): kong = self._create_kong('public', 'private') self.client.login(username='eric', password='test') - ver = Version.objects.create(project=kong, identifier='test id', - verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True) + ver = Version.objects.create( + project=kong, identifier='test id', + verbose_name='test verbose', privacy_level='private', slug='test-slug', active=True, + ) r = self.client.get('/projects/django-kong/builds/') self.assertContains(r, 'test-slug') @@ -370,5 +403,5 @@ class PrivacyTests(TestCase): kong = self._create_kong('public', 'private') self.assertEqual( kong.versions.private().get(slug='latest').slug, - 'latest' + 'latest', ) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index 4c874db49..9bffbb825 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -71,8 +71,10 @@ class URLAccessMixin: val, ('Attribute mismatch for view {view} ({path}): ' '{key} != {expected} (got {value})' - .format(view=name, path=path, key=key, expected=val, - value=resp_val)) + .format( + view=name, path=path, key=key, expected=val, + value=resp_val, + )), ) return response @@ -104,7 +106,8 @@ class URLAccessMixin: url_ctx = self.get_url_path_ctx() if url_ctx: self.response_data = { - url.format(**url_ctx): data for url, data in self.response_data.items()} + url.format(**url_ctx): data for url, data in self.response_data.items() + } for (view, regex, namespace, name) in deconstructed_urls: request_data = self.request_data.get(name, {}).copy() @@ -123,10 +126,14 @@ class URLAccessMixin: # Previous Fixtures self.owner = create_user(username='owner', password='test') self.tester = create_user(username='tester', password='test') - self.pip = get(Project, slug='pip', users=[self.owner], - privacy_level='public', main_language_project=None) - self.private = get(Project, slug='private', privacy_level='private', - main_language_project=None) + self.pip = get( + Project, slug='pip', users=[self.owner], + privacy_level='public', main_language_project=None, + ) + self.private = get( + Project, slug='private', privacy_level='private', + main_language_project=None, + ) class ProjectMixin(URLAccessMixin): @@ -135,8 +142,10 @@ class ProjectMixin(URLAccessMixin): super().setUp() self.build = get(Build, project=self.pip) self.tag = get(Tag, slug='coolness') - self.subproject = get(Project, slug='sub', language='ja', - users=[self.owner], main_language_project=None) + self.subproject = get( + Project, slug='sub', language='ja', + users=[self.owner], main_language_project=None, + ) self.pip.add_subproject(self.subproject) self.pip.translations.add(self.subproject) self.integration = get(Integration, project=self.pip, provider_data='') diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py index 1131de23b..de7c244bf 100644 --- a/readthedocs/rtd_tests/tests/test_profile_views.py +++ b/readthedocs/rtd_tests/tests/test_profile_views.py @@ -24,7 +24,7 @@ class ProfileViewsTest(TestCase): 'first_name': 'Read', 'last_name': 'Docs', 'homepage': 'readthedocs.org', - } + }, ) self.assertTrue(resp.status_code, 200) @@ -46,7 +46,7 @@ class ProfileViewsTest(TestCase): 'first_name': 'a' * 31, 'last_name': 'b' * 31, 'homepage': 'c' * 101, - } + }, ) FORM_ERROR_FORMAT = 'Ensure this value has at most {} characters (it has {}).' @@ -57,20 +57,20 @@ class ProfileViewsTest(TestCase): def test_delete_account(self): resp = self.client.get( - reverse('delete_account') + reverse('delete_account'), ) self.assertEqual(resp.status_code, 200) resp = self.client.post( reverse('delete_account'), data={ 'username': self.user.username, - } + }, ) self.assertEqual(resp.status_code, 302) self.assertEqual(resp['Location'], reverse('homepage')) self.assertFalse( - User.objects.filter(username=self.user.username).exists() + User.objects.filter(username=self.user.username).exists(), ) def test_profile_detail(self): @@ -94,7 +94,7 @@ class ProfileViewsTest(TestCase): def test_account_advertising(self): resp = self.client.get( - reverse('account_advertising') + reverse('account_advertising'), ) self.assertEqual(resp.status_code, 200) self.assertTrue(self.user.profile.allow_ads) diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index 5562d3e80..4a56bb939 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -104,7 +104,8 @@ class TestProject(ProjectMixin, TestCase): full_find_method.return_value = [] with self.assertRaisesMessage( ProjectConfigurationError, - ProjectConfigurationError.NOT_FOUND) as cm: + ProjectConfigurationError.NOT_FOUND, + ) as cm: self.pip.conf_file() @patch('readthedocs.projects.models.Project.find') @@ -116,7 +117,8 @@ class TestProject(ProjectMixin, TestCase): ] with self.assertRaisesMessage( ProjectConfigurationError, - ProjectConfigurationError.MULTIPLE_CONF_FILES) as cm: + ProjectConfigurationError.MULTIPLE_CONF_FILES, + ) as cm: self.pip.conf_file() @@ -163,23 +165,24 @@ class TestProjectTranslations(ProjectMixin, TestCase): self.assertFalse(Project.objects.filter(pk=project_delete.pk).exists()) self.assertTrue(Project.objects.filter(pk=project_keep.pk).exists()) self.assertIsNone( - Project.objects.get(pk=project_keep.pk).main_language_project) + Project.objects.get(pk=project_keep.pk).main_language_project, + ) def test_user_can_add_own_project_as_translation(self): user_a = User.objects.get(username='eric') project_a = get( Project, users=[user_a], - language='en', main_language_project=None + language='en', main_language_project=None, ) project_b = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) self.client.login(username=user_a.username, password='test') self.client.post( reverse('projects_translations', args=[project_a.slug]), - data={'project': project_b.slug} + data={'project': project_b.slug}, ) self.assertEqual(project_a.translations.first(), project_b) @@ -191,20 +194,20 @@ class TestProjectTranslations(ProjectMixin, TestCase): user_a = User.objects.get(username='eric') project_a = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) user_b = User.objects.get(username='tester') # User A and B are owners of project B project_b = get( Project, users=[user_b, user_a], - language='en', main_language_project=None + language='en', main_language_project=None, ) self.client.login(username=user_a.username, password='test') self.client.post( reverse('projects_translations', args=[project_a.slug]), - data={'project': project_b.slug} + data={'project': project_b.slug}, ) self.assertEqual(project_a.translations.first(), project_b) @@ -214,20 +217,20 @@ class TestProjectTranslations(ProjectMixin, TestCase): user_a = User.objects.get(username='eric') project_a = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) user_b = User.objects.get(username='tester') project_b = get( Project, users=[user_b], - language='en', main_language_project=None + language='en', main_language_project=None, ) # User A try to add project B as translation of project A self.client.login(username=user_a.username, password='test') resp = self.client.post( reverse('projects_translations', args=[project_a.slug]), - data={'project': project_b.slug} + data={'project': project_b.slug}, ) self.assertContains(resp, 'Select a valid choice') @@ -243,13 +246,13 @@ class TestProjectTranslations(ProjectMixin, TestCase): user_a = User.objects.get(username='eric') project_a = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) user_b = User.objects.get(username='tester') project_b = get( Project, users=[user_b], - language='en', main_language_project=None + language='en', main_language_project=None, ) project_a.translations.add(project_b) @@ -259,16 +262,16 @@ class TestProjectTranslations(ProjectMixin, TestCase): # Project B is listed under user A translations resp = self.client.get( - reverse('projects_translations', args=[project_a.slug]) + reverse('projects_translations', args=[project_a.slug]), ) self.assertContains(resp, project_b.slug) resp = self.client.post( reverse( 'projects_translations_delete', - args=[project_a.slug, project_b.slug] + args=[project_a.slug, project_b.slug], ), - follow=True + follow=True, ) self.assertEqual(resp.status_code, 200) self.assertNotIn(project_b, project_a.translations.all()) @@ -277,11 +280,11 @@ class TestProjectTranslations(ProjectMixin, TestCase): user_a = User.objects.get(username='eric') project_a = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) project_b = get( Project, users=[user_a], - language='en', main_language_project=None + language='en', main_language_project=None, ) project_a.translations.add(project_b) @@ -290,11 +293,11 @@ class TestProjectTranslations(ProjectMixin, TestCase): user_b = User.objects.get(username='tester') project_c = get( Project, users=[user_b], - language='es', main_language_project=None + language='es', main_language_project=None, ) project_d = get( Project, users=[user_b, user_a], - language='en', main_language_project=None + language='en', main_language_project=None, ) project_d.translations.add(project_c) project_d.save() @@ -305,9 +308,9 @@ class TestProjectTranslations(ProjectMixin, TestCase): resp = self.client.post( reverse( 'projects_translations_delete', - args=[project_a.slug, project_b.slug] + args=[project_a.slug, project_b.slug], ), - follow=True + follow=True, ) self.assertEqual(resp.status_code, 404) self.assertIn(project_b, project_a.translations.all()) @@ -319,9 +322,9 @@ class TestProjectTranslations(ProjectMixin, TestCase): resp = self.client.post( reverse( 'projects_translations_delete', - args=[project_d.slug, project_b.slug] + args=[project_d.slug, project_b.slug], ), - follow=True + follow=True, ) self.assertEqual(resp.status_code, 404) self.assertIn(project_b, project_a.translations.all()) @@ -333,9 +336,9 @@ class TestProjectTranslations(ProjectMixin, TestCase): resp = self.client.post( reverse( 'projects_translations_delete', - args=[project_b.slug, project_b.slug] + args=[project_b.slug, project_b.slug], ), - follow=True + follow=True, ) self.assertEqual(resp.status_code, 404) self.assertIn(project_b, project_a.translations.all()) @@ -345,7 +348,7 @@ class TestProjectTranslations(ProjectMixin, TestCase): project_a = Project.objects.get(slug='read-the-docs') project_b = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) project_a.translations.add(project_b) @@ -362,16 +365,16 @@ class TestProjectTranslations(ProjectMixin, TestCase): resp = self.client.post( reverse( 'projects_edit', - args=[project_a.slug] + args=[project_a.slug], ), data=data, - follow=True + follow=True, ) self.assertEqual(resp.status_code, 200) self.assertContains( resp, 'There is already a "es" translation ' - 'for the read-the-docs project' + 'for the read-the-docs project', ) def test_user_can_change_project_with_same_lang(self): @@ -379,7 +382,7 @@ class TestProjectTranslations(ProjectMixin, TestCase): project_a = Project.objects.get(slug='read-the-docs') project_b = get( Project, users=[user_a], - language='es', main_language_project=None + language='es', main_language_project=None, ) project_a.translations.add(project_b) @@ -396,10 +399,10 @@ class TestProjectTranslations(ProjectMixin, TestCase): resp = self.client.post( reverse( 'projects_edit', - args=[project_a.slug] + args=[project_a.slug], ), data=data, - follow=True + follow=True, ) self.assertEqual(resp.status_code, 200) self.assertNotContains(resp, 'There is already a') @@ -430,7 +433,8 @@ class TestFinishInactiveBuildsTask(TestCase): state=BUILD_STATE_TRIGGERED, ) self.build_2.date = ( - timezone.now() - datetime.timedelta(hours=1)) + timezone.now() - datetime.timedelta(hours=1) + ) self.build_2.save() # Build started an hour ago with custom time (2 hours) @@ -440,7 +444,8 @@ class TestFinishInactiveBuildsTask(TestCase): state=BUILD_STATE_TRIGGERED, ) self.build_3.date = ( - timezone.now() - datetime.timedelta(hours=1)) + timezone.now() - datetime.timedelta(hours=1) + ) self.build_3.save() def test_finish_inactive_builds_task(self): diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index 3e52496a5..cd927eed5 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -160,7 +160,7 @@ class TestProjectForms(TestCase): def test_length_of_tags(self): data = { 'documentation_type': 'sphinx', - 'language': 'en' + 'language': 'en', } data['tags'] = '{},{}'.format('a'*50, 'b'*99) form = ProjectExtraForm(data) @@ -212,7 +212,7 @@ class TestProjectAdvancedForm(TestCase): slug='public-4', active=False, privacy_level=PUBLIC, - identifier='public/4' + identifier='public/4', ) get( Version, @@ -254,7 +254,7 @@ class TestProjectAdvancedForm(TestCase): }, { None, 'master', 'public-1', 'public-2', - 'public-3', 'public/4', 'protected', 'private' + 'public-3', 'public/4', 'protected', 'private', }, ) @@ -275,7 +275,7 @@ class TestTranslationForms(TestCase): self.project_s_fr = self.get_project( lang='fr', - users=[self.user_b, self.user_a] + users=[self.user_b, self.user_a], ) def get_project(self, lang, users, **kwargs): @@ -300,7 +300,7 @@ class TestTranslationForms(TestCase): ] self.assertEqual( {proj_slug for proj_slug, _ in form.fields['project'].choices}, - {project.slug for project in expected_projects} + {project.slug for project in expected_projects}, ) form = TranslationForm( @@ -315,7 +315,7 @@ class TestTranslationForms(TestCase): ] self.assertEqual( {proj_slug for proj_slug, _ in form.fields['project'].choices}, - {project.slug for project in expected_projects} + {project.slug for project in expected_projects}, ) def test_excludes_existing_translations(self): @@ -336,7 +336,7 @@ class TestTranslationForms(TestCase): ] self.assertEqual( {proj_slug for proj_slug, _ in form.fields['project'].choices}, - {project.slug for project in expected_projects} + {project.slug for project in expected_projects}, ) def test_user_cant_add_other_user_project(self): @@ -348,11 +348,11 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'Select a valid choice', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) self.assertNotIn( self.project_f_ar, - [proj_slug for proj_slug, _ in form.fields['project'].choices] + [proj_slug for proj_slug, _ in form.fields['project'].choices], ) def test_user_cant_add_project_with_same_lang(self): @@ -364,7 +364,7 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'Both projects can not have the same language (English).', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) def test_user_cant_add_project_with_same_lang_of_other_translation(self): @@ -379,7 +379,7 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'This project already has a translation for English.', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) def test_no_nesting_translation(self): @@ -394,7 +394,7 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'Select a valid choice', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) def test_no_nesting_translation_case_2(self): @@ -409,7 +409,7 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'A project with existing translations can not', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) def test_not_already_translation(self): @@ -424,7 +424,7 @@ class TestTranslationForms(TestCase): self.assertFalse(form.is_valid()) self.assertIn( 'is already a translation', - ''.join(form.errors['project']) + ''.join(form.errors['project']), ) def test_cant_change_language_to_translation_lang(self): @@ -438,12 +438,12 @@ class TestTranslationForms(TestCase): 'documentation_type': 'sphinx', 'language': 'en', }, - instance=self.project_a_es + instance=self.project_a_es, ) self.assertFalse(form.is_valid()) self.assertIn( 'There is already a "en" translation', - ''.join(form.errors['language']) + ''.join(form.errors['language']), ) # Translation tries to change lang @@ -452,12 +452,12 @@ class TestTranslationForms(TestCase): 'documentation_type': 'sphinx', 'language': 'es', }, - instance=self.project_b_en + instance=self.project_b_en, ) self.assertFalse(form.is_valid()) self.assertIn( 'There is already a "es" translation', - ''.join(form.errors['language']) + ''.join(form.errors['language']), ) # Translation tries to change lang @@ -467,12 +467,12 @@ class TestTranslationForms(TestCase): 'documentation_type': 'sphinx', 'language': 'br', }, - instance=self.project_b_en + instance=self.project_b_en, ) self.assertFalse(form.is_valid()) self.assertIn( 'There is already a "br" translation', - ''.join(form.errors['language']) + ''.join(form.errors['language']), ) def test_can_change_language_to_self_lang(self): @@ -489,7 +489,7 @@ class TestTranslationForms(TestCase): 'documentation_type': 'sphinx', 'language': 'es', }, - instance=self.project_a_es + instance=self.project_a_es, ) self.assertTrue(form.is_valid()) @@ -502,7 +502,7 @@ class TestTranslationForms(TestCase): 'documentation_type': 'sphinx', 'language': 'en', }, - instance=self.project_b_en + instance=self.project_b_en, ) self.assertTrue(form.is_valid()) diff --git a/readthedocs/rtd_tests/tests/test_project_querysets.py b/readthedocs/rtd_tests/tests/test_project_querysets.py index 316525c91..01f02e986 100644 --- a/readthedocs/rtd_tests/tests/test_project_querysets.py +++ b/readthedocs/rtd_tests/tests/test_project_querysets.py @@ -24,12 +24,12 @@ class ProjectQuerySetTests(TestCase): mgr = ChildRelatedProjectQuerySet.as_manager() self.assertEqual( mgr.__class__.__name__, - 'ManagerFromChildRelatedProjectQuerySetBase' + 'ManagerFromChildRelatedProjectQuerySetBase', ) mgr = ParentRelatedProjectQuerySet.as_manager() self.assertEqual( mgr.__class__.__name__, - 'ManagerFromParentRelatedProjectQuerySetBase' + 'ManagerFromParentRelatedProjectQuerySetBase', ) def test_is_active(self): diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index 3e992e6bd..b3075167e 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -42,8 +42,10 @@ def get_filesystem(path, top_level_path=None): if os.path.islink(full_path): fs[child] = { 'type': 'link', - 'target': os.path.relpath(os.path.realpath(full_path), - top_level_path) + 'target': os.path.relpath( + os.path.realpath(full_path), + top_level_path, + ), } elif os.path.isfile(full_path): fs[child] = { @@ -77,47 +79,47 @@ class TempSiteRootTestCase(TestCase): self.mocks = { 'PublicSymlinkBase.CNAME_ROOT': mock.patch( 'readthedocs.core.symlink.PublicSymlinkBase.CNAME_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), 'PublicSymlinkBase.WEB_ROOT': mock.patch( 'readthedocs.core.symlink.PublicSymlinkBase.WEB_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), 'PublicSymlinkBase.PROJECT_CNAME_ROOT': mock.patch( 'readthedocs.core.symlink.PublicSymlinkBase.PROJECT_CNAME_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), 'PrivateSymlinkBase.CNAME_ROOT': mock.patch( 'readthedocs.core.symlink.PrivateSymlinkBase.CNAME_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), 'PrivateSymlinkBase.WEB_ROOT': mock.patch( 'readthedocs.core.symlink.PrivateSymlinkBase.WEB_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), 'PrivateSymlinkBase.PROJECT_CNAME_ROOT': mock.patch( 'readthedocs.core.symlink.PrivateSymlinkBase.PROJECT_CNAME_ROOT', - new_callable=mock.PropertyMock + new_callable=mock.PropertyMock, ), } self.patches = {key: mock.start() for (key, mock) in list(self.mocks.items())} self.patches['PublicSymlinkBase.CNAME_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'public_cname_root' + settings.SITE_ROOT, 'public_cname_root', ) self.patches['PublicSymlinkBase.WEB_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'public_web_root' + settings.SITE_ROOT, 'public_web_root', ) self.patches['PublicSymlinkBase.PROJECT_CNAME_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'public_cname_project' + settings.SITE_ROOT, 'public_cname_project', ) self.patches['PrivateSymlinkBase.CNAME_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'private_cname_root' + settings.SITE_ROOT, 'private_cname_root', ) self.patches['PrivateSymlinkBase.WEB_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'private_web_root' + settings.SITE_ROOT, 'private_web_root', ) self.patches['PrivateSymlinkBase.PROJECT_CNAME_ROOT'].return_value = os.path.join( - settings.SITE_ROOT, 'private_cname_project' + settings.SITE_ROOT, 'private_cname_project', ) def tearDown(self): @@ -134,26 +136,30 @@ class BaseSymlinkCnames: def setUp(self): super().setUp() - self.project = get(Project, slug='kong', privacy_level=self.privacy, - main_language_project=None) + self.project = get( + Project, slug='kong', privacy_level=self.privacy, + main_language_project=None, + ) self.project.versions.update(privacy_level=self.privacy) self.project.save() self.symlink = self.symlink_class(self.project) def test_symlink_cname(self): - self.domain = get(Domain, project=self.project, domain='woot.com', - url='http://woot.com', cname=True) + self.domain = get( + Domain, project=self.project, domain='woot.com', + url='http://woot.com', cname=True, + ) self.symlink.symlink_cnames() filesystem = { 'private_cname_project': { - 'woot.com': {'type': 'link', 'target': 'user_builds/kong'} + 'woot.com': {'type': 'link', 'target': 'user_builds/kong'}, }, 'private_cname_root': { 'woot.com': {'type': 'link', 'target': 'private_web_root/kong'}, }, 'private_web_root': {'kong': {'en': {}}}, 'public_cname_project': { - 'woot.com': {'type': 'link', 'target': 'user_builds/kong'} + 'woot.com': {'type': 'link', 'target': 'user_builds/kong'}, }, 'public_cname_root': { 'woot.com': {'type': 'link', 'target': 'public_web_root/kong'}, @@ -162,8 +168,8 @@ class BaseSymlinkCnames: 'kong': {'en': {'latest': { 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', - }}} - } + }}}, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -173,8 +179,10 @@ class BaseSymlinkCnames: self.assertFilesystem(filesystem) def test_symlink_remove_orphan_symlinks(self): - self.domain = get(Domain, project=self.project, domain='woot.com', - url='http://woot.com', cname=True) + self.domain = get( + Domain, project=self.project, domain='woot.com', + url='http://woot.com', cname=True, + ) self.symlink.symlink_cnames() # Editing the Domain and calling save will symlink the new domain and @@ -204,8 +212,8 @@ class BaseSymlinkCnames: 'kong': {'en': {'latest': { 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', - }}} - } + }}}, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -257,19 +265,21 @@ class BaseSymlinkCnames: def test_symlink_cname_dont_link_missing_domains(self): """Domains should be relinked after deletion.""" - self.domain = get(Domain, project=self.project, domain='woot.com', - url='http://woot.com', cname=True) + self.domain = get( + Domain, project=self.project, domain='woot.com', + url='http://woot.com', cname=True, + ) self.symlink.symlink_cnames() filesystem = { 'private_cname_project': { - 'woot.com': {'type': 'link', 'target': 'user_builds/kong'} + 'woot.com': {'type': 'link', 'target': 'user_builds/kong'}, }, 'private_cname_root': { 'woot.com': {'type': 'link', 'target': 'private_web_root/kong'}, }, 'private_web_root': {'kong': {'en': {}}}, 'public_cname_project': { - 'woot.com': {'type': 'link', 'target': 'user_builds/kong'} + 'woot.com': {'type': 'link', 'target': 'user_builds/kong'}, }, 'public_cname_root': { 'woot.com': {'type': 'link', 'target': 'public_web_root/kong'}, @@ -278,8 +288,8 @@ class BaseSymlinkCnames: 'kong': {'en': {'latest': { 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', - }}} - } + }}}, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -310,12 +320,16 @@ class BaseSubprojects: def setUp(self): super().setUp() - self.project = get(Project, slug='kong', privacy_level=self.privacy, - main_language_project=None) + self.project = get( + Project, slug='kong', privacy_level=self.privacy, + main_language_project=None, + ) self.project.versions.update(privacy_level=self.privacy) self.project.save() - self.subproject = get(Project, slug='sub', privacy_level=self.privacy, - main_language_project=None) + self.subproject = get( + Project, slug='sub', privacy_level=self.privacy, + main_language_project=None, + ) self.subproject.versions.update(privacy_level=self.privacy) self.subproject.save() self.symlink = self.symlink_class(self.project) @@ -343,16 +357,16 @@ class BaseSubprojects: 'sub': { 'type': 'link', 'target': 'public_web_root/sub', - } - } + }, + }, }, 'sub': { 'en': {'latest': { 'type': 'link', 'target': 'user_builds/sub/rtd-builds/latest', - }} - } - } + }}, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -390,15 +404,15 @@ class BaseSubprojects: 'type': 'link', 'target': 'public_web_root/sub', }, - } + }, }, 'sub': { 'en': {'latest': { 'type': 'link', 'target': 'user_builds/sub/rtd-builds/latest', - }} - } - } + }}, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -437,15 +451,15 @@ class BaseSubprojects: 'type': 'link', 'target': 'public_web_root/sub', }, - } + }, }, 'sub': { 'en': {'latest': { 'type': 'link', 'target': 'user_builds/sub/rtd-builds/latest', - }} - } - } + }}, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -479,16 +493,16 @@ class BaseSubprojects: 'sub': { 'type': 'link', 'target': 'public_web_root/sub', - } - } + }, + }, }, 'sub': { 'en': {'latest': { 'type': 'link', 'target': 'user_builds/sub/rtd-builds/latest', - }} - } - } + }}, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -521,21 +535,29 @@ class BaseSymlinkTranslations: def setUp(self): super().setUp() - self.project = get(Project, slug='kong', privacy_level=self.privacy, - main_language_project=None) + self.project = get( + Project, slug='kong', privacy_level=self.privacy, + main_language_project=None, + ) self.project.versions.update(privacy_level=self.privacy) self.project.save() - self.translation = get(Project, slug='pip', language='de', - privacy_level=self.privacy, - main_language_project=None) + self.translation = get( + Project, slug='pip', language='de', + privacy_level=self.privacy, + main_language_project=None, + ) self.translation.versions.update(privacy_level=self.privacy) self.translation.save() self.project.translations.add(self.translation) self.symlink = self.symlink_class(self.project) - get(Version, slug='master', verbose_name='master', active=True, - project=self.project, privacy_level=self.privacy) - get(Version, slug='master', verbose_name='master', active=True, - project=self.translation, privacy_level=self.privacy) + get( + Version, slug='master', verbose_name='master', active=True, + project=self.project, privacy_level=self.privacy, + ) + get( + Version, slug='master', verbose_name='master', active=True, + project=self.translation, privacy_level=self.privacy, + ) self.assertIn(self.translation, self.project.translations.all()) def test_symlink_basic(self): @@ -577,9 +599,9 @@ class BaseSymlinkTranslations: 'type': 'link', 'target': 'user_builds/pip/rtd-builds/master', }, - } - } - } + }, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -632,9 +654,9 @@ class BaseSymlinkTranslations: 'type': 'link', 'target': 'user_builds/pip/rtd-builds/master', }, - } - } - } + }, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -689,9 +711,9 @@ class BaseSymlinkTranslations: 'type': 'link', 'target': 'user_builds/pip/rtd-builds/master', }, - } - } - } + }, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -738,9 +760,9 @@ class BaseSymlinkTranslations: 'type': 'link', 'target': 'user_builds/pip/rtd-builds/master', }, - } - } - } + }, + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -774,8 +796,10 @@ class BaseSymlinkSingleVersion: def setUp(self): super().setUp() - self.project = get(Project, slug='kong', privacy_level=self.privacy, - main_language_project=None) + self.project = get( + Project, slug='kong', privacy_level=self.privacy, + main_language_project=None, + ) self.project.versions.update(privacy_level=self.privacy) self.project.save() self.version = self.project.versions.get(slug='latest') @@ -798,7 +822,7 @@ class BaseSymlinkSingleVersion: 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', }, - } + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -824,8 +848,8 @@ class BaseSymlinkSingleVersion: 'kong': { 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', - } - } + }, + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -849,13 +873,17 @@ class BaseSymlinkVersions: def setUp(self): super().setUp() - self.project = get(Project, slug='kong', privacy_level=self.privacy, - main_language_project=None) + self.project = get( + Project, slug='kong', privacy_level=self.privacy, + main_language_project=None, + ) self.project.versions.update(privacy_level=self.privacy) self.project.save() - self.stable = get(Version, slug='stable', verbose_name='stable', - active=True, project=self.project, - privacy_level=self.privacy) + self.stable = get( + Version, slug='stable', verbose_name='stable', + active=True, project=self.project, + privacy_level=self.privacy, + ) self.project.versions.update(privacy_level=self.privacy) self.symlink = self.symlink_class(self.project) @@ -882,7 +910,7 @@ class BaseSymlinkVersions: }, }, }, - } + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -912,7 +940,7 @@ class BaseSymlinkVersions: 'target': 'user_builds/kong/rtd-builds/stable', }, }}, - } + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -951,7 +979,7 @@ class BaseSymlinkVersions: 'type': 'link', 'target': 'user_builds/kong/rtd-builds/latest', }}}, - } + }, } if self.privacy == 'private': public_root = filesystem['public_web_root'].copy() @@ -975,11 +1003,15 @@ class TestPublicSymlinkUnicode(TempSiteRootTestCase): def setUp(self): super().setUp() - self.project = get(Project, slug='kong', name='foo-∫', - main_language_project=None) + self.project = get( + Project, slug='kong', name='foo-∫', + main_language_project=None, + ) self.project.save() - self.stable = get(Version, slug='foo-a', verbose_name='foo-∂', - active=True, project=self.project) + self.stable = get( + Version, slug='foo-a', verbose_name='foo-∂', + active=True, project=self.project, + ) self.symlink = PublicSymlink(self.project) def test_symlink_no_error(self): @@ -1045,13 +1077,15 @@ class TestPublicPrivateSymlink(TempSiteRootTestCase): self.user = get(User) self.project = get( Project, name='project', slug='project', privacy_level='public', - users=[self.user], main_language_project=None) + users=[self.user], main_language_project=None, + ) self.project.versions.update(privacy_level='public') self.project.save() self.subproject = get( Project, name='subproject', slug='subproject', privacy_level='public', - users=[self.user], main_language_project=None) + users=[self.user], main_language_project=None, + ) self.subproject.versions.update(privacy_level='public') self.subproject.save() @@ -1156,11 +1190,13 @@ class TestPublicPrivateSymlink(TempSiteRootTestCase): self.client.force_login(self.user) self.client.post( - reverse('project_version_detail', - kwargs={ - 'project_slug': self.subproject.slug, - 'version_slug': self.subproject.versions.first().slug, - }), + reverse( + 'project_version_detail', + kwargs={ + 'project_slug': self.subproject.slug, + 'version_slug': self.subproject.versions.first().slug, + }, + ), data={'privacy_level': 'private', 'active': True}, ) @@ -1168,10 +1204,12 @@ class TestPublicPrivateSymlink(TempSiteRootTestCase): self.assertTrue(self.subproject.versions.first().active) self.client.post( - reverse('projects_advanced', - kwargs={ - 'project_slug': self.subproject.slug, - }), + reverse( + 'projects_advanced', + kwargs={ + 'project_slug': self.subproject.slug, + }, + ), data={ # Required defaults 'python_interpreter': 'python', diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py index b2d5c752c..0e7225ac6 100644 --- a/readthedocs/rtd_tests/tests/test_project_views.py +++ b/readthedocs/rtd_tests/tests/test_project_views.py @@ -171,7 +171,8 @@ class TestAdvancedForm(TestBasicsForm): del self.step_data['extra']['tags'] self.assertCountEqual( [tag.name for tag in proj.tags.all()], - ['bar', 'baz', 'foo']) + ['bar', 'baz', 'foo'], + ) data.update(self.step_data['extra']) for (key, val) in list(data.items()): self.assertEqual(getattr(proj, key), val) @@ -202,8 +203,10 @@ class TestAdvancedForm(TestBasicsForm): self.assertIsNotNone(proj) self.assertEqual(proj.remote_repository, remote_repo) - @patch('readthedocs.projects.views.private.ProjectExtraForm.clean_description', - create=True) + @patch( + 'readthedocs.projects.views.private.ProjectExtraForm.clean_description', + create=True, + ) def test_form_spam(self, mocked_validator): """Don't add project on a spammy description.""" self.user.date_joined = timezone.now() - timedelta(days=365) @@ -224,8 +227,10 @@ class TestAdvancedForm(TestBasicsForm): proj = Project.objects.get(name='foobar') self.assertFalse(self.user.profile.banned) - @patch('readthedocs.projects.views.private.ProjectExtraForm.clean_description', - create=True) + @patch( + 'readthedocs.projects.views.private.ProjectExtraForm.clean_description', + create=True, + ) def test_form_spam_ban_user(self, mocked_validator): """Don't add spam and ban new user.""" self.user.date_joined = timezone.now() @@ -278,8 +283,10 @@ class TestImportDemoView(MockBuildTestCase): messages = list(resp_redir.context['messages']) self.assertEqual(messages[0].level, message_const.SUCCESS) - self.assertEqual(project, - Project.objects.get(slug='eric-demo')) + self.assertEqual( + project, + Project.objects.get(slug='eric-demo'), + ) def test_import_demo_another_user_imported(self): """Import demo project after another user, expect success.""" @@ -312,11 +319,15 @@ class TestImportDemoView(MockBuildTestCase): self.assertEqual(resp_redir.status_code, 200) messages = list(resp_redir.context['messages']) self.assertEqual(messages[0].level, message_const.SUCCESS) - self.assertRegex(messages[0].message, - r'already imported') + self.assertRegex( + messages[0].message, + r'already imported', + ) - self.assertEqual(project, - Project.objects.get(slug='eric-demo')) + self.assertEqual( + project, + Project.objects.get(slug='eric-demo'), + ) def test_import_demo_imported_duplicate(self): """ @@ -339,11 +350,15 @@ class TestImportDemoView(MockBuildTestCase): self.assertEqual(resp_redir.status_code, 200) messages = list(resp_redir.context['messages']) self.assertEqual(messages[0].level, message_const.ERROR) - self.assertRegex(messages[0].message, - r'There was a problem') + self.assertRegex( + messages[0].message, + r'There was a problem', + ) - self.assertEqual(project, - Project.objects.get(slug='eric-demo')) + self.assertEqual( + project, + Project.objects.get(slug='eric-demo'), + ) class TestPrivateViews(MockBuildTestCase): @@ -380,7 +395,8 @@ class TestPrivateViews(MockBuildTestCase): broadcast.assert_called_with( type='app', task=tasks.remove_dirs, - args=[(project.doc_path,)]) + args=[(project.doc_path,)], + ) def test_subproject_create(self): project = get(Project, slug='pip', users=[self.user]) @@ -395,7 +411,8 @@ class TestPrivateViews(MockBuildTestCase): broadcast.assert_called_with( type='app', task=tasks.symlink_subproject, - args=[project.pk]) + args=[project.pk], + ) class TestPrivateMixins(MockBuildTestCase): diff --git a/readthedocs/rtd_tests/tests/test_redirects.py b/readthedocs/rtd_tests/tests/test_redirects.py index 834b46568..6fbafebf3 100644 --- a/readthedocs/rtd_tests/tests/test_redirects.py +++ b/readthedocs/rtd_tests/tests/test_redirects.py @@ -22,16 +22,19 @@ class RedirectTests(TestCase): self.client.login(username='eric', password='test') self.client.post( '/dashboard/import/', - {'repo_type': 'git', 'name': 'Pip', - 'tags': 'big, fucking, monkey', 'default_branch': '', - 'project_url': 'http://pip.rtfd.org', - 'repo': 'https://github.com/fail/sauce', - 'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be', - 'default_version': LATEST, - 'privacy_level': 'public', - 'version_privacy_level': 'public', - 'description': 'wat', - 'documentation_type': 'sphinx'}) + { + 'repo_type': 'git', 'name': 'Pip', + 'tags': 'big, fucking, monkey', 'default_branch': '', + 'project_url': 'http://pip.rtfd.org', + 'repo': 'https://github.com/fail/sauce', + 'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be', + 'default_version': LATEST, + 'privacy_level': 'public', + 'version_privacy_level': 'public', + 'description': 'wat', + 'documentation_type': 'sphinx', + }, + ) pip = Project.objects.get(slug='pip') pip.versions.create_latest() @@ -43,14 +46,17 @@ class RedirectTests(TestCase): r = self.client.get('/docs/pip/') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://readthedocs.org/docs/pip/en/latest/') + r['Location'], 'http://readthedocs.org/docs/pip/en/latest/', + ) # Specific Page Redirects def test_proper_page_on_main_site(self): r = self.client.get('/docs/pip/page/test.html') self.assertEqual(r.status_code, 302) - self.assertEqual(r['Location'], - 'http://readthedocs.org/docs/pip/en/latest/test.html') + self.assertEqual( + r['Location'], + 'http://readthedocs.org/docs/pip/en/latest/test.html', + ) # If slug is neither valid lang nor valid version, it should 404. # TODO: This should 404 directly, not redirect first @@ -88,15 +94,18 @@ class RedirectTests(TestCase): r = self.client.get('/', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/') + r['Location'], 'http://pip.readthedocs.org/en/latest/', + ) # Specific Page Redirects @override_settings(USE_SUBDOMAIN=True) def test_proper_page_on_subdomain(self): r = self.client.get('/page/test.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) - self.assertEqual(r['Location'], - 'http://pip.readthedocs.org/en/latest/test.html') + self.assertEqual( + r['Location'], + 'http://pip.readthedocs.org/en/latest/test.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_improper_subdomain_filename_only(self): @@ -112,16 +121,19 @@ class RedirectAppTests(TestCase): self.client.login(username='eric', password='test') self.client.post( '/dashboard/import/', - {'repo_type': 'git', 'name': 'Pip', - 'tags': 'big, fucking, monkey', 'default_branch': '', - 'project_url': 'http://pip.rtfd.org', - 'repo': 'https://github.com/fail/sauce', - 'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be', - 'default_version': LATEST, - 'privacy_level': 'public', - 'version_privacy_level': 'public', - 'description': 'wat', - 'documentation_type': 'sphinx'}) + { + 'repo_type': 'git', 'name': 'Pip', + 'tags': 'big, fucking, monkey', 'default_branch': '', + 'project_url': 'http://pip.rtfd.org', + 'repo': 'https://github.com/fail/sauce', + 'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be', + 'default_version': LATEST, + 'privacy_level': 'public', + 'version_privacy_level': 'public', + 'description': 'wat', + 'documentation_type': 'sphinx', + }, + ) self.pip = Project.objects.get(slug='pip') self.pip.versions.create_latest() @@ -143,12 +155,14 @@ class RedirectAppTests(TestCase): r = self.client.get('/redirect', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/redirect.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/redirect.html', + ) r = self.client.get('/redirect/', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/redirect/') + r['Location'], 'http://pip.readthedocs.org/en/latest/redirect/', + ) r = self.client.get('/en/latest/redirect/', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 404) @@ -156,33 +170,37 @@ class RedirectAppTests(TestCase): @override_settings(USE_SUBDOMAIN=True) def test_redirect_root(self): Redirect.objects.create( - project=self.pip, redirect_type='prefix', from_url='/woot/') + project=self.pip, redirect_type='prefix', from_url='/woot/', + ) r = self.client.get('/woot/faq.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_page(self): Redirect.objects.create( project=self.pip, redirect_type='page', - from_url='/install.html', to_url='/tutorial/install.html' + from_url='/install.html', to_url='/tutorial/install.html', ) r = self.client.get('/install.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/tutorial/install.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/tutorial/install.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_exact(self): Redirect.objects.create( project=self.pip, redirect_type='exact', - from_url='/en/latest/install.html', to_url='/en/latest/tutorial/install.html' + from_url='/en/latest/install.html', to_url='/en/latest/tutorial/install.html', ) r = self.client.get('/en/latest/install.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/tutorial/install.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/tutorial/install.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_exact_with_rest(self): @@ -200,7 +218,8 @@ class RedirectAppTests(TestCase): r = self.client.get('/en/latest/guides/install.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/version/guides/install.html') + r['Location'], 'http://pip.readthedocs.org/en/version/guides/install.html', + ) Redirect.objects.create( project=self.pip, redirect_type='exact', @@ -209,7 +228,8 @@ class RedirectAppTests(TestCase): r = self.client.get('/es/version/guides/install.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/master/guides/install.html') + r['Location'], 'http://pip.readthedocs.org/en/master/guides/install.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_inactive_version(self): @@ -234,75 +254,94 @@ class RedirectAppTests(TestCase): r = self.client.get('/en/oldversion/', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/newversion/') + r['Location'], 'http://pip.readthedocs.org/en/newversion/', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_keeps_version_number(self): Redirect.objects.create( project=self.pip, redirect_type='page', - from_url='/how_to_install.html', to_url='/install.html') + from_url='/how_to_install.html', to_url='/install.html', + ) with patch('readthedocs.core.views.serve._serve_symlink_docs') as _serve_docs: _serve_docs.side_effect = Http404() - r = self.client.get('/en/0.8.1/how_to_install.html', - HTTP_HOST='pip.readthedocs.org') + r = self.client.get( + '/en/0.8.1/how_to_install.html', + HTTP_HOST='pip.readthedocs.org', + ) self.assertEqual(r.status_code, 302) self.assertEqual( r['Location'], - 'http://pip.readthedocs.org/en/0.8.1/install.html') + 'http://pip.readthedocs.org/en/0.8.1/install.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_keeps_language(self): Redirect.objects.create( project=self.pip, redirect_type='page', - from_url='/how_to_install.html', to_url='/install.html') + from_url='/how_to_install.html', to_url='/install.html', + ) with patch('readthedocs.core.views.serve._serve_symlink_docs') as _serve_docs: _serve_docs.side_effect = Http404() - r = self.client.get('/de/0.8.1/how_to_install.html', - HTTP_HOST='pip.readthedocs.org') + r = self.client.get( + '/de/0.8.1/how_to_install.html', + HTTP_HOST='pip.readthedocs.org', + ) self.assertEqual(r.status_code, 302) self.assertEqual( r['Location'], - 'http://pip.readthedocs.org/de/0.8.1/install.html') + 'http://pip.readthedocs.org/de/0.8.1/install.html', + ) @override_settings(USE_SUBDOMAIN=True) def test_redirect_recognizes_custom_cname(self): Redirect.objects.create( project=self.pip, redirect_type='page', from_url='/install.html', - to_url='/tutorial/install.html') - r = self.client.get('/install.html', - HTTP_HOST='pip.pypa.io', - HTTP_X_RTD_SLUG='pip') + to_url='/tutorial/install.html', + ) + r = self.client.get( + '/install.html', + HTTP_HOST='pip.pypa.io', + HTTP_X_RTD_SLUG='pip', + ) self.assertEqual(r.status_code, 302) self.assertEqual( r['Location'], - 'http://pip.pypa.io/en/latest/tutorial/install.html') + 'http://pip.pypa.io/en/latest/tutorial/install.html', + ) @override_settings(USE_SUBDOMAIN=True, PYTHON_MEDIA=True) def test_redirect_html(self): Redirect.objects.create( - project=self.pip, redirect_type='sphinx_html') + project=self.pip, redirect_type='sphinx_html', + ) r = self.client.get('/en/latest/faq/', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html', + ) @override_settings(USE_SUBDOMAIN=True, PYTHON_MEDIA=True) def test_redirect_html_index(self): Redirect.objects.create( - project=self.pip, redirect_type='sphinx_html') + project=self.pip, redirect_type='sphinx_html', + ) r = self.client.get('/en/latest/faq/index.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html') + r['Location'], 'http://pip.readthedocs.org/en/latest/faq.html', + ) @override_settings(USE_SUBDOMAIN=True, PYTHON_MEDIA=True) def test_redirect_htmldir(self): Redirect.objects.create( - project=self.pip, redirect_type='sphinx_htmldir') + project=self.pip, redirect_type='sphinx_htmldir', + ) r = self.client.get('/en/latest/faq.html', HTTP_HOST='pip.readthedocs.org') self.assertEqual(r.status_code, 302) self.assertEqual( - r['Location'], 'http://pip.readthedocs.org/en/latest/faq/') + r['Location'], 'http://pip.readthedocs.org/en/latest/faq/', + ) class CustomRedirectTests(TestCase): @@ -340,11 +379,13 @@ class RedirectBuildTests(TestCase): fixtures = ['eric', 'test_data'] def setUp(self): - self.project = get(Project, - slug='project-1', - documentation_type='sphinx', - conf_py_file='test_conf.py', - versions=[fixture()]) + self.project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='test_conf.py', + versions=[fixture()], + ) self.version = self.project.versions.all()[0] def test_redirect_list(self): @@ -369,37 +410,37 @@ class GetFullPathTests(TestCase): def test_http_filenames_return_themselves(self): self.assertEqual( self.redirect.get_full_path('http://rtfd.org'), - 'http://rtfd.org' + 'http://rtfd.org', ) def test_redirects_no_subdomain(self): self.assertEqual( self.redirect.get_full_path('index.html'), - '/docs/read-the-docs/en/latest/' + '/docs/read-the-docs/en/latest/', ) @override_settings( - USE_SUBDOMAIN=True, PRODUCTION_DOMAIN='rtfd.org' + USE_SUBDOMAIN=True, PRODUCTION_DOMAIN='rtfd.org', ) def test_redirects_with_subdomain(self): self.assertEqual( self.redirect.get_full_path('faq.html'), - '/en/latest/faq.html' + '/en/latest/faq.html', ) @override_settings( - USE_SUBDOMAIN=True, PRODUCTION_DOMAIN='rtfd.org' + USE_SUBDOMAIN=True, PRODUCTION_DOMAIN='rtfd.org', ) def test_single_version_with_subdomain(self): self.redirect.project.single_version = True self.assertEqual( self.redirect.get_full_path('faq.html'), - '/faq.html' + '/faq.html', ) def test_single_version_no_subdomain(self): self.redirect.project.single_version = True self.assertEqual( self.redirect.get_full_path('faq.html'), - '/docs/read-the-docs/faq.html' + '/docs/read-the-docs/faq.html', ) diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py index 2918f1ce0..81f2f3ef5 100644 --- a/readthedocs/rtd_tests/tests/test_resolver.py +++ b/readthedocs/rtd_tests/tests/test_resolver.py @@ -67,7 +67,8 @@ class SmartResolverPathTests(ResolverBase): url = resolve_path(project=self.pip, filename='foo/bar/index.html') self.assertEqual(url, '/docs/pip/en/latest/foo/bar/') url = resolve_path( - project=self.pip, filename='foo/index/index.html') + project=self.pip, filename='foo/index/index.html', + ) self.assertEqual(url, '/docs/pip/en/latest/foo/index/') def test_resolver_filename_false_index(self): @@ -75,9 +76,11 @@ class SmartResolverPathTests(ResolverBase): url = resolve_path(project=self.pip, filename='foo/foo_index.html') self.assertEqual(url, '/docs/pip/en/latest/foo/foo_index.html') url = resolve_path( - project=self.pip, filename='foo_index/foo_index.html') + project=self.pip, filename='foo_index/foo_index.html', + ) self.assertEqual( - url, '/docs/pip/en/latest/foo_index/foo_index.html') + url, '/docs/pip/en/latest/foo_index/foo_index.html', + ) def test_resolver_filename_sphinx(self): self.pip.documentation_type = 'sphinx' @@ -173,21 +176,25 @@ class ResolverPathOverrideTests(ResolverBase): self.pip.single_version = False with override_settings(USE_SUBDOMAIN=False): url = resolve_path( - project=self.pip, filename='index.html', single_version=True) + project=self.pip, filename='index.html', single_version=True, + ) self.assertEqual(url, '/docs/pip/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( - project=self.pip, filename='index.html', single_version=True) + project=self.pip, filename='index.html', single_version=True, + ) self.assertEqual(url, '/') def test_resolver_force_domain(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( - project=self.pip, filename='index.html', cname=True) + project=self.pip, filename='index.html', cname=True, + ) self.assertEqual(url, '/en/latest/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( - project=self.pip, filename='index.html', cname=True) + project=self.pip, filename='index.html', cname=True, + ) self.assertEqual(url, '/en/latest/') def test_resolver_force_domain_single_version(self): @@ -195,66 +202,78 @@ class ResolverPathOverrideTests(ResolverBase): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( project=self.pip, filename='index.html', single_version=True, - cname=True) + cname=True, + ) self.assertEqual(url, '/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( project=self.pip, filename='index.html', single_version=True, - cname=True) + cname=True, + ) self.assertEqual(url, '/') def test_resolver_force_language(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( - project=self.pip, filename='index.html', language='cz') + project=self.pip, filename='index.html', language='cz', + ) self.assertEqual(url, '/docs/pip/cz/latest/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( - project=self.pip, filename='index.html', language='cz') + project=self.pip, filename='index.html', language='cz', + ) self.assertEqual(url, '/cz/latest/') def test_resolver_force_version(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( - project=self.pip, filename='index.html', version_slug='foo') + project=self.pip, filename='index.html', version_slug='foo', + ) self.assertEqual(url, '/docs/pip/en/foo/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( - project=self.pip, filename='index.html', version_slug='foo') + project=self.pip, filename='index.html', version_slug='foo', + ) self.assertEqual(url, '/en/foo/') def test_resolver_force_language_version(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( project=self.pip, filename='index.html', language='cz', - version_slug='foo') + version_slug='foo', + ) self.assertEqual(url, '/docs/pip/cz/foo/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( project=self.pip, filename='index.html', language='cz', - version_slug='foo') + version_slug='foo', + ) self.assertEqual(url, '/cz/foo/') def test_resolver_no_force_translation(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( - project=self.translation, filename='index.html', language='cz') + project=self.translation, filename='index.html', language='cz', + ) self.assertEqual(url, '/docs/pip/ja/latest/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( - project=self.translation, filename='index.html', language='cz') + project=self.translation, filename='index.html', language='cz', + ) self.assertEqual(url, '/ja/latest/') def test_resolver_no_force_translation_with_version(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_path( project=self.translation, filename='index.html', language='cz', - version_slug='foo') + version_slug='foo', + ) self.assertEqual(url, '/docs/pip/ja/foo/') with override_settings(USE_SUBDOMAIN=True): url = resolve_path( project=self.translation, filename='index.html', language='cz', - version_slug='foo') + version_slug='foo', + ) self.assertEqual(url, '/ja/foo/') @@ -274,7 +293,7 @@ class ResolverCanonicalProject(TestCase): proj1.save() self.assertEqual( proj1.main_language_project.main_language_project, - proj1 + proj1, ) # This tests that we aren't going to re-recurse back to resolving proj1 @@ -428,7 +447,8 @@ class ResolverDomainTests(ResolverBase): @override_settings( PRODUCTION_DOMAIN='readthedocs.org', - PUBLIC_DOMAIN='public.readthedocs.org') + PUBLIC_DOMAIN='public.readthedocs.org', + ) def test_domain_public(self): with override_settings(USE_SUBDOMAIN=False): url = resolve_domain(project=self.translation) @@ -492,11 +512,13 @@ class ResolverTests(ResolverBase): with override_settings(USE_SUBDOMAIN=False): url = resolve(project=self.subproject) self.assertEqual( - url, 'http://readthedocs.org/docs/pip/projects/sub/ja/latest/') + url, 'http://readthedocs.org/docs/pip/projects/sub/ja/latest/', + ) with override_settings(USE_SUBDOMAIN=True): url = resolve(project=self.subproject) self.assertEqual( - url, 'http://pip.readthedocs.org/projects/sub/ja/latest/') + url, 'http://pip.readthedocs.org/projects/sub/ja/latest/', + ) @override_settings(PRODUCTION_DOMAIN='readthedocs.org') def test_resolver_translation(self): @@ -576,7 +598,8 @@ class ResolverTests(ResolverBase): @override_settings( PRODUCTION_DOMAIN='readthedocs.org', - PUBLIC_DOMAIN='public.readthedocs.org') + PUBLIC_DOMAIN='public.readthedocs.org', + ) def test_resolver_public_domain_overrides(self): with override_settings(USE_SUBDOMAIN=False): url = resolve(project=self.pip, private=True) @@ -586,10 +609,12 @@ class ResolverTests(ResolverBase): with override_settings(USE_SUBDOMAIN=True): url = resolve(project=self.pip, private=True) self.assertEqual( - url, 'http://pip.public.readthedocs.org/en/latest/') + url, 'http://pip.public.readthedocs.org/en/latest/', + ) url = resolve(project=self.pip, private=False) self.assertEqual( - url, 'http://pip.public.readthedocs.org/en/latest/') + url, 'http://pip.public.readthedocs.org/en/latest/', + ) # Domain overrides PUBLIC_DOMAIN self.domain = fixture.get( @@ -720,20 +745,24 @@ class TestSubprojectsWithTranslations(TestCase): self.assertEqual( url, 'http://{project.slug}.readthedocs.io/en/latest/'.format( project=self.superproject_en, - )) + ), + ) url = resolve(self.superproject_es, filename='') self.assertEqual( url, 'http://{project.slug}.readthedocs.io/es/latest/'.format( project=self.superproject_en, - )) + ), + ) url = resolve(self.subproject_en, filename='') # yapf: disable self.assertEqual( url, - ('http://{project.slug}.readthedocs.io/projects/' - '{subproject.slug}/en/latest/').format( + ( + 'http://{project.slug}.readthedocs.io/projects/' + '{subproject.slug}/en/latest/' + ).format( project=self.superproject_en, subproject=self.subproject_en, ), @@ -742,8 +771,10 @@ class TestSubprojectsWithTranslations(TestCase): url = resolve(self.subproject_es, filename='') self.assertEqual( url, - ('http://{project.slug}.readthedocs.io/projects/' - '{subproject.slug}/es/latest/').format( + ( + 'http://{project.slug}.readthedocs.io/projects/' + '{subproject.slug}/es/latest/' + ).format( project=self.superproject_en, subproject=self.subproject_en, ), @@ -770,8 +801,10 @@ class TestSubprojectsWithTranslations(TestCase): url = resolve(self.subproject_en, filename='') self.assertEqual( url, - ('http://docs.example.com/projects/' - '{subproject.slug}/en/latest/').format( + ( + 'http://docs.example.com/projects/' + '{subproject.slug}/en/latest/' + ).format( subproject=self.subproject_en, ), ) @@ -779,8 +812,10 @@ class TestSubprojectsWithTranslations(TestCase): url = resolve(self.subproject_es, filename='') self.assertEqual( url, - ('http://docs.example.com/projects/' - '{subproject.slug}/es/latest/').format( + ( + 'http://docs.example.com/projects/' + '{subproject.slug}/es/latest/' + ).format( subproject=self.subproject_en, ), ) diff --git a/readthedocs/rtd_tests/tests/test_restapi_client.py b/readthedocs/rtd_tests/tests/test_restapi_client.py index 7f75d0f5e..cf555b748 100644 --- a/readthedocs/rtd_tests/tests/test_restapi_client.py +++ b/readthedocs/rtd_tests/tests/test_restapi_client.py @@ -6,7 +6,7 @@ from readthedocs.restapi.client import DrfJsonSerializer class TestDrfJsonSerializer(TestCase): data = { - 'proper': 'json' + 'proper': 'json', } serialized_data = '{"proper":"json"}' diff --git a/readthedocs/rtd_tests/tests/test_search_json_parsing.py b/readthedocs/rtd_tests/tests/test_search_json_parsing.py index d2ac34e34..42b0839c4 100644 --- a/readthedocs/rtd_tests/tests/test_search_json_parsing.py +++ b/readthedocs/rtd_tests/tests/test_search_json_parsing.py @@ -15,7 +15,7 @@ class TestHacks(TestCase): os.path.join( base_dir, 'files/api.fjson', - ) + ), ) self.assertEqual(data['sections'][1]['id'], 'a-basic-api-client-using-slumber') # Only capture h2's after the first section diff --git a/readthedocs/rtd_tests/tests/test_single_version.py b/readthedocs/rtd_tests/tests/test_single_version.py index 567e96b5f..e69b80375 100644 --- a/readthedocs/rtd_tests/tests/test_single_version.py +++ b/readthedocs/rtd_tests/tests/test_single_version.py @@ -7,7 +7,7 @@ from readthedocs.projects.models import Project @override_settings( - USE_SUBDOMAIN=True, PUBLIC_DOMAIN='public.readthedocs.org', SERVE_PUBLIC_DOCS=True + USE_SUBDOMAIN=True, PUBLIC_DOMAIN='public.readthedocs.org', SERVE_PUBLIC_DOCS=True, ) class RedirectSingleVersionTests(TestCase): @@ -16,16 +16,24 @@ class RedirectSingleVersionTests(TestCase): def test_docs_url_generation(self): with override_settings(USE_SUBDOMAIN=False): - self.assertEqual(self.pip.get_docs_url(), - 'http://readthedocs.org/docs/pip/') + self.assertEqual( + self.pip.get_docs_url(), + 'http://readthedocs.org/docs/pip/', + ) with override_settings(USE_SUBDOMAIN=True): - self.assertEqual(self.pip.get_docs_url(), - 'http://pip.public.readthedocs.org/') + self.assertEqual( + self.pip.get_docs_url(), + 'http://pip.public.readthedocs.org/', + ) self.pip.single_version = False with override_settings(USE_SUBDOMAIN=False): - self.assertEqual(self.pip.get_docs_url(), - 'http://readthedocs.org/docs/pip/en/latest/') + self.assertEqual( + self.pip.get_docs_url(), + 'http://readthedocs.org/docs/pip/en/latest/', + ) with override_settings(USE_SUBDOMAIN=True): - self.assertEqual(self.pip.get_docs_url(), - 'http://pip.public.readthedocs.org/en/latest/') + self.assertEqual( + self.pip.get_docs_url(), + 'http://pip.public.readthedocs.org/en/latest/', + ) diff --git a/readthedocs/rtd_tests/tests/test_subprojects.py b/readthedocs/rtd_tests/tests/test_subprojects.py index d6545189e..255325d7a 100644 --- a/readthedocs/rtd_tests/tests/test_subprojects.py +++ b/readthedocs/rtd_tests/tests/test_subprojects.py @@ -18,13 +18,13 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {}, project=project, - user=user + user=user, ) form.full_clean() self.assertEqual(len(form.errors['child']), 1) self.assertRegex( form.errors['child'][0], - r'This field is required.' + r'This field is required.', ) def test_nonexistent_child(self): @@ -34,13 +34,13 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': 9999}, project=project, - user=user + user=user, ) form.full_clean() self.assertEqual(len(form.errors['child']), 1) self.assertRegex( form.errors['child'][0], - r'Select a valid choice.' + r'Select a valid choice.', ) def test_adding_subproject_fails_when_user_is_not_admin(self): @@ -57,13 +57,13 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': subproject.pk}, project=project, - user=user + user=user, ) form.full_clean() self.assertEqual(len(form.errors['child']), 1) self.assertRegex( form.errors['child'][0], - r'Select a valid choice.' + r'Select a valid choice.', ) def test_adding_subproject_passes_when_user_is_admin(self): @@ -81,14 +81,14 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': subproject.pk}, project=project, - user=user + user=user, ) form.full_clean() self.assertTrue(form.is_valid()) form.save() self.assertEqual( [r.child for r in project.subprojects.all()], - [subproject] + [subproject], ) def test_subproject_form_cant_create_sub_sub_project(self): @@ -97,7 +97,7 @@ class SubprojectFormTests(TestCase): subproject = fixture.get(Project, users=[user]) subsubproject = fixture.get(Project, users=[user]) relation = fixture.get( - ProjectRelationship, parent=project, child=subproject + ProjectRelationship, parent=project, child=subproject, ) self.assertQuerysetEqual( Project.objects.for_admin_user(user), @@ -108,7 +108,7 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': subsubproject.pk}, project=subproject, - user=user + user=user, ) # The subsubproject is valid here, as far as the child check is # concerned, but the parent check should fail. @@ -120,7 +120,7 @@ class SubprojectFormTests(TestCase): self.assertEqual(len(form.errors['parent']), 1) self.assertRegex( form.errors['parent'][0], - r'Subproject nesting is not supported' + r'Subproject nesting is not supported', ) def test_excludes_existing_subprojects(self): @@ -128,7 +128,7 @@ class SubprojectFormTests(TestCase): project = fixture.get(Project, users=[user]) subproject = fixture.get(Project, users=[user]) relation = fixture.get( - ProjectRelationship, parent=project, child=subproject + ProjectRelationship, parent=project, child=subproject, ) self.assertQuerysetEqual( Project.objects.for_admin_user(user), @@ -139,7 +139,7 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': subproject.pk}, project=project, - user=user + user=user, ) self.assertEqual( [proj_id for (proj_id, __) in form.fields['child'].choices], @@ -153,12 +153,12 @@ class SubprojectFormTests(TestCase): form = ProjectRelationshipForm( {'child': project.pk}, project=project, - user=user + user=user, ) self.assertFalse(form.is_valid()) self.assertNotIn( project.id, - [proj_id for (proj_id, __) in form.fields['child'].choices] + [proj_id for (proj_id, __) in form.fields['child'].choices], ) @@ -170,12 +170,16 @@ class ResolverBase(TestCase): self.owner = create_user(username='owner', password='test') self.tester = create_user(username='tester', password='test') self.pip = fixture.get(Project, slug='pip', users=[self.owner], main_language_project=None) - self.subproject = fixture.get(Project, slug='sub', language='ja', - users=[ self.owner], - main_language_project=None) - self.translation = fixture.get(Project, slug='trans', language='ja', - users=[ self.owner], - main_language_project=None) + self.subproject = fixture.get( + Project, slug='sub', language='ja', + users=[ self.owner], + main_language_project=None, + ) + self.translation = fixture.get( + Project, slug='trans', language='ja', + users=[ self.owner], + main_language_project=None, + ) self.pip.add_subproject(self.subproject) self.pip.translations.add(self.translation) @@ -187,13 +191,13 @@ class ResolverBase(TestCase): @override_settings( PRODUCTION_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False, - ) + ) def test_resolver_subproject_alias(self): resp = self.client.get('/docs/pip/projects/sub_alias/') self.assertEqual(resp.status_code, 302) self.assertEqual( resp._headers['location'][1], - 'http://readthedocs.org/docs/pip/projects/sub_alias/ja/latest/' + 'http://readthedocs.org/docs/pip/projects/sub_alias/ja/latest/', ) @override_settings(USE_SUBDOMAIN=True) @@ -202,5 +206,5 @@ class ResolverBase(TestCase): self.assertEqual(resp.status_code, 302) self.assertEqual( resp._headers['location'][1], - 'http://pip.readthedocs.org/projects/sub_alias/ja/latest/' + 'http://pip.readthedocs.org/projects/sub_alias/ja/latest/', ) diff --git a/readthedocs/rtd_tests/tests/test_sync_versions.py b/readthedocs/rtd_tests/tests/test_sync_versions.py index d5686dcec..37339d2a0 100644 --- a/readthedocs/rtd_tests/tests/test_sync_versions.py +++ b/readthedocs/rtd_tests/tests/test_sync_versions.py @@ -169,7 +169,7 @@ class TestSyncVersions(TestCase): } self.assertTrue( - Version.objects.filter(slug='0.8.3').exists() + Version.objects.filter(slug='0.8.3').exists(), ) self.client.post( @@ -180,7 +180,7 @@ class TestSyncVersions(TestCase): # There isn't a v0.8.3 self.assertFalse( - Version.objects.filter(slug='0.8.3').exists() + Version.objects.filter(slug='0.8.3').exists(), ) def test_machine_attr_when_user_define_stable_tag_and_delete_it(self): @@ -205,7 +205,7 @@ class TestSyncVersions(TestCase): # 0.8.3 is the current stable self.assertEqual( version8.identifier, - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -239,7 +239,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( '1abc2def3', - current_stable.identifier + current_stable.identifier, ) # Deleting the tag should return the RTD's stable @@ -270,7 +270,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( '0.8.3', - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -317,7 +317,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( '1abc2def3', - current_stable.identifier + current_stable.identifier, ) # User activates the stable version @@ -352,7 +352,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( '0.8.3', - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -380,7 +380,7 @@ class TestSyncVersions(TestCase): # 0.8.3 is the current stable self.assertEqual( '0.8.3', - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -412,7 +412,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( 'origin/stable', - current_stable.identifier + current_stable.identifier, ) # Deleting the branch should return the RTD's stable @@ -441,7 +441,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( 'origin/0.8.3', - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -484,7 +484,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( 'origin/stable', - current_stable.identifier + current_stable.identifier, ) # User activates the stable version @@ -517,7 +517,7 @@ class TestSyncVersions(TestCase): current_stable = self.pip.get_stable_version() self.assertEqual( 'origin/0.8.3', - current_stable.identifier + current_stable.identifier, ) self.assertTrue(current_stable.machine) @@ -554,7 +554,7 @@ class TestSyncVersions(TestCase): version_latest = self.pip.versions.get(slug='latest') self.assertEqual( '1abc2def3', - version_latest.identifier + version_latest.identifier, ) # Deleting the tag should return the RTD's latest @@ -565,7 +565,7 @@ class TestSyncVersions(TestCase): 'verbose_name': 'master', }, ], - 'tags': [] + 'tags': [], } resp = self.client.post( @@ -579,7 +579,7 @@ class TestSyncVersions(TestCase): version_latest = self.pip.versions.get(slug='latest') self.assertEqual( 'master', - version_latest.identifier + version_latest.identifier, ) self.assertTrue(version_latest.machine) @@ -614,7 +614,7 @@ class TestSyncVersions(TestCase): version_latest = self.pip.versions.get(slug='latest') self.assertEqual( 'origin/latest', - version_latest.identifier + version_latest.identifier, ) # Deleting the branch should return the RTD's latest @@ -668,7 +668,7 @@ class TestSyncVersions(TestCase): # We only have one version with an identifier `1234` self.assertEqual( self.pip.versions.filter(identifier='1234').count(), - 1 + 1, ) # We add a new tag with the same identifier @@ -701,7 +701,7 @@ class TestSyncVersions(TestCase): # We have two versions with an identifier `1234` self.assertEqual( self.pip.versions.filter(identifier='1234').count(), - 2 + 2, ) # We delete one version with identifier `1234` @@ -730,7 +730,7 @@ class TestSyncVersions(TestCase): # We have only one version with an identifier `1234` self.assertEqual( self.pip.versions.filter(identifier='1234').count(), - 1 + 1, ) @@ -826,7 +826,7 @@ class TestStableVersion(TestCase): 'tags': [ { 'identifier': 'this.is.invalid', - 'verbose_name': 'this.is.invalid' + 'verbose_name': 'this.is.invalid', }, ], } @@ -847,7 +847,7 @@ class TestStableVersion(TestCase): }, { 'identifier': 'this.is.invalid', - 'verbose_name': 'this.is.invalid' + 'verbose_name': 'this.is.invalid', }, ], } @@ -897,7 +897,7 @@ class TestStableVersion(TestCase): 'identifier': '1.0.0', 'verbose_name': '1.0.0', }, - ] + ], } self.client.post( @@ -1109,12 +1109,12 @@ class TestStableVersion(TestCase): self.assertTrue(version_stable.active) self.assertEqual( '1abc2def3', - self.pip.get_stable_version().identifier + self.pip.get_stable_version().identifier, ) # There arent others stable slugs like stable_a other_stable = self.pip.versions.filter( - slug__startswith='stable_' + slug__startswith='stable_', ) self.assertFalse(other_stable.exists()) @@ -1131,10 +1131,10 @@ class TestStableVersion(TestCase): self.assertTrue(version_stable.active) self.assertEqual( '1abc2def3', - self.pip.get_stable_version().identifier + self.pip.get_stable_version().identifier, ) other_stable = self.pip.versions.filter( - slug__startswith='stable_' + slug__startswith='stable_', ) self.assertFalse(other_stable.exists()) @@ -1197,11 +1197,11 @@ class TestStableVersion(TestCase): self.assertTrue(version_stable.active) self.assertEqual( 'origin/stable', - self.pip.get_stable_version().identifier + self.pip.get_stable_version().identifier, ) # There arent others stable slugs like stable_a other_stable = self.pip.versions.filter( - slug__startswith='stable_' + slug__startswith='stable_', ) self.assertFalse(other_stable.exists()) @@ -1218,10 +1218,10 @@ class TestStableVersion(TestCase): self.assertTrue(version_stable.active) self.assertEqual( 'origin/stable', - self.pip.get_stable_version().identifier + self.pip.get_stable_version().identifier, ) other_stable = self.pip.versions.filter( - slug__startswith='stable_' + slug__startswith='stable_', ) self.assertFalse(other_stable.exists()) @@ -1277,12 +1277,12 @@ class TestLatestVersion(TestCase): self.assertTrue(version_latest.active) self.assertEqual( '1abc2def3', - version_latest.identifier + version_latest.identifier, ) # There arent others latest slugs like latest_a other_latest = self.pip.versions.filter( - slug__startswith='latest_' + slug__startswith='latest_', ) self.assertFalse(other_latest.exists()) @@ -1299,10 +1299,10 @@ class TestLatestVersion(TestCase): self.assertTrue(version_latest.active) self.assertEqual( '1abc2def3', - version_latest.identifier + version_latest.identifier, ) other_latest = self.pip.versions.filter( - slug__startswith='latest_' + slug__startswith='latest_', ) self.assertFalse(other_latest.exists()) @@ -1334,12 +1334,12 @@ class TestLatestVersion(TestCase): self.assertTrue(version_latest.active) self.assertEqual( 'origin/latest', - version_latest.identifier + version_latest.identifier, ) # There arent others latest slugs like latest_a other_latest = self.pip.versions.filter( - slug__startswith='latest_' + slug__startswith='latest_', ) self.assertFalse(other_latest.exists()) @@ -1356,9 +1356,9 @@ class TestLatestVersion(TestCase): self.assertTrue(version_latest.active) self.assertEqual( 'origin/latest', - version_latest.identifier + version_latest.identifier, ) other_latest = self.pip.versions.filter( - slug__startswith='latest_' + slug__startswith='latest_', ) self.assertFalse(other_latest.exists()) diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py index cfa81237e..b01c99976 100644 --- a/readthedocs/rtd_tests/tests/test_urls.py +++ b/readthedocs/rtd_tests/tests/test_urls.py @@ -47,7 +47,7 @@ class TestVersionURLs(TestCase): def test_version_url_with_caps(self): url = reverse( 'project_download_media', - kwargs={'type_': 'pdf', 'version_slug': '1.4.X', 'project_slug': 'django'} + kwargs={'type_': 'pdf', 'version_slug': '1.4.X', 'project_slug': 'django'}, ) self.assertTrue(url) @@ -57,18 +57,18 @@ class TestProfileDetailURLs(TestCase): def test_profile_detail_url(self): url = reverse( 'profiles_profile_detail', - kwargs={'username': 'foo+bar'} - ) + kwargs={'username': 'foo+bar'}, + ) self.assertEqual(url, '/profiles/foo+bar/') url = reverse( 'profiles_profile_detail', - kwargs={'username': 'abc+def@ghi.jkl'} - ) + kwargs={'username': 'abc+def@ghi.jkl'}, + ) self.assertEqual(url, '/profiles/abc+def@ghi.jkl/') url = reverse( 'profiles_profile_detail', - kwargs={'username': 'abc-def+ghi'} - ) + kwargs={'username': 'abc-def+ghi'}, + ) self.assertEqual(url, '/profiles/abc-def+ghi/') diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py index 2dfd47258..bf181c3d5 100644 --- a/readthedocs/rtd_tests/tests/test_version_commit_name.py +++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py @@ -16,43 +16,59 @@ class VersionCommitNameTests(TestCase): def test_branch_name_made_friendly_when_sha(self): commit_hash = '3d92b728b7d7b842259ac2020c2fa389f13aff0d' - version = new(Version, identifier=commit_hash, - slug=STABLE, verbose_name=STABLE, type=TAG) + version = new( + Version, identifier=commit_hash, + slug=STABLE, verbose_name=STABLE, type=TAG, + ) # we shorten commit hashes to keep things readable self.assertEqual(version.identifier_friendly, '3d92b728') def test_branch_name(self): - version = new(Version, identifier='release-2.5.x', - slug='release-2.5.x', verbose_name='release-2.5.x', - type=BRANCH) + version = new( + Version, identifier='release-2.5.x', + slug='release-2.5.x', verbose_name='release-2.5.x', + type=BRANCH, + ) self.assertEqual(version.commit_name, 'release-2.5.x') def test_tag_name(self): - version = new(Version, identifier='10f1b29a2bd2', slug='release-2.5.0', - verbose_name='release-2.5.0', type=TAG) + version = new( + Version, identifier='10f1b29a2bd2', slug='release-2.5.0', + verbose_name='release-2.5.0', type=TAG, + ) self.assertEqual(version.commit_name, 'release-2.5.0') def test_branch_with_name_stable(self): - version = new(Version, identifier='origin/stable', slug=STABLE, - verbose_name='stable', type=BRANCH) + version = new( + Version, identifier='origin/stable', slug=STABLE, + verbose_name='stable', type=BRANCH, + ) self.assertEqual(version.commit_name, 'stable') def test_stable_version_tag(self): - version = new(Version, - identifier='3d92b728b7d7b842259ac2020c2fa389f13aff0d', - slug=STABLE, verbose_name=STABLE, type=TAG) - self.assertEqual(version.commit_name, - '3d92b728b7d7b842259ac2020c2fa389f13aff0d') + version = new( + Version, + identifier='3d92b728b7d7b842259ac2020c2fa389f13aff0d', + slug=STABLE, verbose_name=STABLE, type=TAG, + ) + self.assertEqual( + version.commit_name, + '3d92b728b7d7b842259ac2020c2fa389f13aff0d', + ) def test_hg_latest_branch(self): hg_project = get(Project, repo_type=REPO_TYPE_HG) - version = new(Version, identifier='default', slug=LATEST, - verbose_name=LATEST, type=BRANCH, project=hg_project) + version = new( + Version, identifier='default', slug=LATEST, + verbose_name=LATEST, type=BRANCH, project=hg_project, + ) self.assertEqual(version.commit_name, 'default') def test_git_latest_branch(self): git_project = get(Project, repo_type=REPO_TYPE_GIT) - version = new(Version, project=git_project, - identifier='origin/master', slug=LATEST, - verbose_name=LATEST, type=BRANCH) + version = new( + Version, project=git_project, + identifier='origin/master', slug=LATEST, + verbose_name=LATEST, type=BRANCH, + ) self.assertEqual(version.commit_name, 'master') diff --git a/readthedocs/rtd_tests/tests/test_version_config.py b/readthedocs/rtd_tests/tests/test_version_config.py index f97c9c871..2bcd61104 100644 --- a/readthedocs/rtd_tests/tests/test_version_config.py +++ b/readthedocs/rtd_tests/tests/test_version_config.py @@ -16,12 +16,12 @@ class VersionConfigTests(TestCase): build_old = Build.objects.create( project=self.project, version=self.version, - config={'version': 1} + config={'version': 1}, ) build_new = Build.objects.create( project=self.project, version=self.version, - config={'version': 2} + config={'version': 2}, ) build_new_error = Build.objects.create( project=self.project, @@ -42,7 +42,7 @@ class VersionConfigTests(TestCase): Build, project=self.project, version=self.version, - config={} + config={}, ) build_old.config = {'version': 1} build_old.save() @@ -51,7 +51,7 @@ class VersionConfigTests(TestCase): Build, project=self.project, version=self.version, - config={} + config={}, ) build_new.config = {'version': 1} build_new.save() diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py index 63d3ae385..a0d4b4b28 100644 --- a/readthedocs/rtd_tests/tests/test_version_slug.py +++ b/readthedocs/rtd_tests/tests/test_version_slug.py @@ -35,58 +35,68 @@ class VersionSlugFieldTests(TestCase): def test_saving(self): version = Version.objects.create( verbose_name='1.0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, '1.0') def test_normalizing(self): version = Version.objects.create( verbose_name='1%0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, '1-0') def test_normalizing_slashes(self): version = Version.objects.create( verbose_name='releases/1.0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, 'releases-1.0') def test_uppercase(self): version = Version.objects.create( verbose_name='SomeString-charclass', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, 'somestring-charclass') def test_placeholder_as_name(self): version = Version.objects.create( verbose_name='-', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, 'unknown') def test_multiple_empty_names(self): version = Version.objects.create( verbose_name='-', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, 'unknown') version = Version.objects.create( verbose_name='-./.-', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, 'unknown_a') def test_uniqueness(self): version = Version.objects.create( verbose_name='1!0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, '1-0') version = Version.objects.create( verbose_name='1%0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, '1-0_a') version = Version.objects.create( verbose_name='1?0', - project=self.pip) + project=self.pip, + ) self.assertEqual(version.slug, '1-0_b') def test_uniquifying_suffix(self): diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py index ca9cc42ed..21515ad88 100644 --- a/readthedocs/rtd_tests/tests/test_views.py +++ b/readthedocs/rtd_tests/tests/test_views.py @@ -151,7 +151,7 @@ class PrivateViewsAreProtectedTests(TestCase): def test_project_translations_delete(self): response = self.client.get( - '/dashboard/pip/translations/delete/a-translation/' + '/dashboard/pip/translations/delete/a-translation/', ) self.assertRedirectToLogin(response) @@ -214,13 +214,13 @@ class SubprojectViewTests(TestCase): def test_deny_delete_for_non_project_admins(self): response = self.client.get( - '/dashboard/my-mainproject/subprojects/delete/my-subproject/' + '/dashboard/my-mainproject/subprojects/delete/my-subproject/', ) self.assertEqual(response.status_code, 404) self.assertTrue( self.subproject in - [r.child for r in self.project.subprojects.all()] + [r.child for r in self.project.subprojects.all()], ) def test_admins_can_delete_subprojects(self): @@ -239,7 +239,7 @@ class SubprojectViewTests(TestCase): self.assertEqual(response.status_code, 405) self.assertTrue( self.subproject in - [r.child for r in self.project.subprojects.all()] + [r.child for r in self.project.subprojects.all()], ) # Test POST response = self.client.post( @@ -248,11 +248,11 @@ class SubprojectViewTests(TestCase): self.assertEqual(response.status_code, 302) self.assertTrue( self.subproject not in - [r.child for r in self.project.subprojects.all()] + [r.child for r in self.project.subprojects.all()], ) def test_project_admins_can_delete_subprojects_that_they_are_not_admin_of( - self + self, ): self.project.users.add(self.user) self.assertFalse(AdminPermission.is_admin(self.user, self.subproject)) @@ -263,7 +263,7 @@ class SubprojectViewTests(TestCase): self.assertEqual(response.status_code, 302) self.assertTrue( self.subproject not in - [r.child for r in self.project.subprojects.all()] + [r.child for r in self.project.subprojects.all()], ) diff --git a/readthedocs/rtd_tests/utils.py b/readthedocs/rtd_tests/utils.py index 95c0d9938..79f6eb0bb 100644 --- a/readthedocs/rtd_tests/utils.py +++ b/readthedocs/rtd_tests/utils.py @@ -34,7 +34,7 @@ def get_readthedocs_app_path(): def check_output(command, env=None): output = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=env + env=env, ).communicate()[0] log.info(output) return output @@ -53,7 +53,7 @@ def make_test_git(): # the repo check_output(['git', 'checkout', '-b', 'submodule', 'master'], env=env) add_git_submodule_without_cloning( - directory, 'foobar', 'https://foobar.com/git' + directory, 'foobar', 'https://foobar.com/git', ) check_output(['git', 'add', '.'], env=env) check_output(['git', 'commit', '-m"Add submodule"'], env=env) @@ -61,7 +61,7 @@ def make_test_git(): # Add an invalid submodule URL in the invalidsubmodule branch check_output(['git', 'checkout', '-b', 'invalidsubmodule', 'master'], env=env) add_git_submodule_without_cloning( - directory, 'invalid', 'git@github.com:rtfd/readthedocs.org.git' + directory, 'invalid', 'git@github.com:rtfd/readthedocs.org.git', ) check_output(['git', 'add', '.'], env=env) check_output(['git', 'commit', '-m"Add invalid submodule"'], env=env) @@ -122,11 +122,11 @@ def make_git_repo(directory, name='sample_repo'): check_output(['git', 'init'] + [directory], env=env) check_output( ['git', 'config', 'user.email', 'dev@readthedocs.org'], - env=env + env=env, ) check_output( ['git', 'config', 'user.name', 'Read the Docs'], - env=env + env=env, ) # Set up the actual repository @@ -179,8 +179,10 @@ def delete_git_branch(directory, branch): @restoring_chdir -def create_git_submodule(directory, submodule, - msg='Add realative submodule', branch='master'): +def create_git_submodule( + directory, submodule, + msg='Add realative submodule', branch='master', +): env = environ.copy() env['GIT_DIR'] = pjoin(directory, '.git') chdir(directory) diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py index a9fe07b02..f8c88a419 100644 --- a/readthedocs/search/indexes.py +++ b/readthedocs/search/indexes.py @@ -206,7 +206,7 @@ class Index: 'remove': { 'index': old_index, 'alias': self._index, - } + }, }) actions.append({'add': {'index': new_index, 'alias': self._index}}) @@ -237,7 +237,7 @@ class ProjectIndex(Index): 'id': {'type': 'long'}, 'name': {'type': 'string', 'analyzer': 'default_icu'}, 'description': { - 'type': 'string', 'analyzer': 'default_icu' + 'type': 'string', 'analyzer': 'default_icu', }, 'slug': {'type': 'string', 'index': 'not_analyzed'}, 'lang': {'type': 'string', 'index': 'not_analyzed'}, @@ -266,7 +266,7 @@ class ProjectIndex(Index): doc = {} attrs = ( - 'id', 'name', 'slug', 'description', 'lang', 'tags', 'author', 'url' + 'id', 'name', 'slug', 'description', 'lang', 'tags', 'author', 'url', ) for attr in attrs: doc[attr] = data.get(attr, '') @@ -367,7 +367,7 @@ class SectionIndex(Index): 'type': 'object', 'properties': { 'code': { - 'type': 'string', 'analyzer': 'default_icu' + 'type': 'string', 'analyzer': 'default_icu', }, }, }, @@ -384,7 +384,7 @@ class SectionIndex(Index): attrs = ( 'id', 'project', 'title', 'page_id', 'version', 'path', 'content', - 'commit' + 'commit', ) for attr in attrs: doc[attr] = data.get(attr, '') diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index a1f0cc209..16297e641 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -44,7 +44,7 @@ def search_project(request, query, language=None): if language: body['facets']['language']['facet_filter'] = { - 'term': {'lang': language} + 'term': {'lang': language}, } body['filter'] = {'term': {'lang': language}} @@ -54,7 +54,7 @@ def search_project(request, query, language=None): def search_file( - request, query, project_slug=None, version_slug=LATEST, taxonomy=None + request, query, project_slug=None, version_slug=LATEST, taxonomy=None, ): """ Search index for files matching query. @@ -79,7 +79,7 @@ def search_file( 'boost': 10, 'slop': 2, }, - } + }, }, { 'match_phrase': { @@ -88,7 +88,7 @@ def search_file( 'boost': 5, 'slop': 3, }, - } + }, }, { 'match_phrase': { @@ -96,7 +96,7 @@ def search_file( 'query': query, 'slop': 5, }, - } + }, }, ], }, @@ -142,7 +142,7 @@ def search_file( ) ) final_filter['and'].append({ - 'terms': {'project': project_slugs} + 'terms': {'project': project_slugs}, }) # Add routing to optimize search by hitting the right shard. @@ -212,7 +212,7 @@ def search_section( 'boost': 10, 'slop': 2, }, - } + }, }, { 'match_phrase': { @@ -220,7 +220,7 @@ def search_section( 'query': query, 'slop': 5, }, - } + }, }, ], }, diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py index 354df8b15..8f99aa012 100644 --- a/readthedocs/search/parse_json.py +++ b/readthedocs/search/parse_json.py @@ -27,8 +27,10 @@ def process_all_json_files(version, build_dir=True): html_files = [] for root, _, files in os.walk(full_path): for filename in fnmatch.filter(files, '*.fjson'): - if filename in ['search.fjson', 'genindex.fjson', - 'py-modindex.fjson']: + if filename in [ + 'search.fjson', 'genindex.fjson', + 'py-modindex.fjson', + ]: continue html_files.append(os.path.join(root, filename)) page_list = [] diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py index fa740285d..bfb7058fc 100644 --- a/readthedocs/search/tests/test_views.py +++ b/readthedocs/search/tests/test_views.py @@ -35,8 +35,10 @@ class TestElasticSearch: self._reindex_elasticsearch(es_index=es_index) def test_search_by_project_name(self, client, project): - result, _ = self._get_search_result(url=self.url, client=client, - search_params={'q': project.name}) + result, _ = self._get_search_result( + url=self.url, client=client, + search_params={'q': project.name}, + ) assert project.name.encode('utf-8') in result.text().encode('utf-8') @@ -46,8 +48,10 @@ class TestElasticSearch: G(Project, language='bn', name=project.name) self._reindex_elasticsearch(es_index=es_index) - result, page = self._get_search_result(url=self.url, client=client, - search_params={'q': project.name}) + result, page = self._get_search_result( + url=self.url, client=client, + search_params={'q': project.name}, + ) content = page.find('.navigable .language-list') # There should be 2 languages @@ -61,8 +65,10 @@ class TestElasticSearch: self._reindex_elasticsearch(es_index=es_index) search_params = {'q': project.name, 'language': 'bn'} - result, page = self._get_search_result(url=self.url, client=client, - search_params=search_params) + result, page = self._get_search_result( + url=self.url, client=client, + search_params=search_params, + ) # There should be only 1 result assert len(result) == 1 @@ -75,11 +81,15 @@ class TestElasticSearch: @pytest.mark.parametrize('data_type', ['content', 'headers', 'title']) @pytest.mark.parametrize('page_num', [0, 1]) def test_search_by_file_content(self, client, project, data_type, page_num): - query = get_search_query_from_project_file(project_slug=project.slug, page_num=page_num, - data_type=data_type) + query = get_search_query_from_project_file( + project_slug=project.slug, page_num=page_num, + data_type=data_type, + ) - result, _ = self._get_search_result(url=self.url, client=client, - search_params={'q': query, 'type': 'file'}) + result, _ = self._get_search_result( + url=self.url, client=client, + search_params={'q': query, 'type': 'file'}, + ) assert len(result) == 1 def test_file_search_show_projects(self, client): @@ -88,8 +98,10 @@ class TestElasticSearch: # `Github` word is present both in `kuma` and `pipeline` files # so search with this phrase - result, page = self._get_search_result(url=self.url, client=client, - search_params={'q': 'GitHub', 'type': 'file'}) + result, page = self._get_search_result( + url=self.url, client=client, + search_params={'q': 'GitHub', 'type': 'file'}, + ) # There should be 2 search result assert len(result) == 2 @@ -108,8 +120,10 @@ class TestElasticSearch: # `Github` word is present both in `kuma` and `pipeline` files # so search with this phrase but filter through `kuma` project search_params = {'q': 'GitHub', 'type': 'file', 'project': 'kuma'} - result, page = self._get_search_result(url=self.url, client=client, - search_params=search_params) + result, page = self._get_search_result( + url=self.url, client=client, + search_params=search_params, + ) # There should be 1 search result as we have filtered assert len(result) == 1 @@ -139,8 +153,10 @@ class TestElasticSearch: query = get_search_query_from_project_file(project_slug=project.slug) - result, page = self._get_search_result(url=self.url, client=client, - search_params={'q': query, 'type': 'file'}) + result, page = self._get_search_result( + url=self.url, client=client, + search_params={'q': query, 'type': 'file'}, + ) # There should be only one result because by default # only latest version result should be there @@ -172,7 +188,9 @@ class TestElasticSearch: # Now search with subproject content but explicitly filter by the parent project query = get_search_query_from_project_file(project_slug=subproject.slug) search_params = {'q': query, 'type': 'file', 'project': project.slug} - result, page = self._get_search_result(url=self.url, client=client, - search_params=search_params) + result, page = self._get_search_result( + url=self.url, client=client, + search_params=search_params, + ) assert len(result) == 1 diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py index a55da2dfa..1e50b8414 100644 --- a/readthedocs/search/utils.py +++ b/readthedocs/search/utils.py @@ -37,10 +37,10 @@ def process_mkdocs_json(version, build_dir=True): relative_path = parse_path_from_file(file_path=filename) html = parse_content_from_file(file_path=filename) headers = parse_headers_from_file( - documentation_type='mkdocs', file_path=filename + documentation_type='mkdocs', file_path=filename, ) sections = parse_sections_from_file( - documentation_type='mkdocs', file_path=filename + documentation_type='mkdocs', file_path=filename, ) try: title = sections[0]['title'] @@ -81,7 +81,7 @@ def valid_mkdocs_json(file_path): if to_check not in page_json: log.warning( '(Search Index) Unable to index file: %s error: Invalid JSON', - file_path + file_path, ) return None @@ -137,7 +137,7 @@ def parse_content_from_file(file_path): if not content: log.info( - '(Search Index) Unable to index file: %s, empty file', file_path + '(Search Index) Unable to index file: %s, empty file', file_path, ) else: log.debug('(Search Index) %s length: %s', file_path, len(content)) diff --git a/readthedocs/urls.py b/readthedocs/urls.py index de18fcd3f..cb723c64f 100644 --- a/readthedocs/urls.py +++ b/readthedocs/urls.py @@ -75,7 +75,7 @@ api_urls = [ url(r'^api/v2/', include('readthedocs.restapi.urls')), url( r'^api-auth/', - include('rest_framework.urls', namespace='rest_framework') + include('rest_framework.urls', namespace='rest_framework'), ), ] @@ -121,7 +121,7 @@ debug_urls += [ # Export URLs groups = [ basic_urls, rtd_urls, project_urls, api_urls, core_urls, i18n_urls, - deprecated_urls + deprecated_urls, ] if settings.DO_NOT_TRACK_ENABLED: diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py index fa51ed0ae..e228ac720 100644 --- a/readthedocs/vcs_support/backends/bzr.py +++ b/readthedocs/vcs_support/backends/bzr.py @@ -88,6 +88,6 @@ class Backend(BaseVCS): exit_code, stdout, stderr = self.run('bzr', 'switch', identifier) if exit_code != 0: raise RepositoryError( - RepositoryError.FAILED_TO_CHECKOUT.format(identifier) + RepositoryError.FAILED_TO_CHECKOUT.format(identifier), ) return exit_code, stdout, stderr diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index 7bde178c3..aa3adc6c2 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -73,8 +73,10 @@ class Backend(BaseVCS): submodules_in_config = ( config.submodules.exclude != ALL or config.submodules.include ) - if (self.project.has_feature(Feature.SKIP_SUBMODULES) or - not submodules_in_config): + if ( + self.project.has_feature(Feature.SKIP_SUBMODULES) or + not submodules_in_config + ): return False # Keep compatibility with previous projects @@ -160,7 +162,7 @@ class Backend(BaseVCS): code, out, err = self.run('git', 'checkout', '--force', revision) if code != 0: raise RepositoryError( - RepositoryError.FAILED_TO_CHECKOUT.format(revision) + RepositoryError.FAILED_TO_CHECKOUT.format(revision), ) return [code, out, err] diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py index fd97ff2cc..515e74a77 100644 --- a/readthedocs/vcs_support/backends/hg.py +++ b/readthedocs/vcs_support/backends/hg.py @@ -109,10 +109,10 @@ class Backend(BaseVCS): if not identifier: identifier = 'tip' exit_code, stdout, stderr = self.run( - 'hg', 'update', '--clean', identifier + 'hg', 'update', '--clean', identifier, ) if exit_code != 0: raise RepositoryError( - RepositoryError.FAILED_TO_CHECKOUT.format(identifier) + RepositoryError.FAILED_TO_CHECKOUT.format(identifier), ) return exit_code, stdout, stderr diff --git a/readthedocs/vcs_support/tests.py b/readthedocs/vcs_support/tests.py index d43f8c0f4..0a271f52e 100644 --- a/readthedocs/vcs_support/tests.py +++ b/readthedocs/vcs_support/tests.py @@ -31,23 +31,31 @@ class TestNonBlockingLock(unittest.TestCase): self.version_mock.slug = 'test-version-slug' def test_simplelock(self): - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, + ) as f_lock: self.assertTrue(os.path.exists(f_lock.fpath)) def test_simplelock_cleanup(self): lock_path = None - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, + ) as f_lock: lock_path = f_lock.fpath self.assertTrue(lock_path is not None and not os.path.exists(lock_path)) def test_nonreentrant(self): - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, + ) as f_lock: try: - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, + ) as f_lock: pass except utils.LockTimeout: pass @@ -55,11 +63,15 @@ class TestNonBlockingLock(unittest.TestCase): raise AssertionError('Should have thrown LockTimeout') def test_forceacquire(self): - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, + ) as f_lock: try: - with utils.NonBlockingLock(project=self.project_mock, - version=self.version_mock, max_lock_age=0) as f_lock: + with utils.NonBlockingLock( + project=self.project_mock, + version=self.version_mock, max_lock_age=0, + ) as f_lock: pass except utils.LockTimeout: raise AssertionError('Should have thrown LockTimeout') diff --git a/readthedocs/vcs_support/utils.py b/readthedocs/vcs_support/utils.py index 65f9e3839..c948d36fd 100644 --- a/readthedocs/vcs_support/utils.py +++ b/readthedocs/vcs_support/utils.py @@ -29,7 +29,7 @@ class Lock: def __init__(self, project, version, timeout=5, polling_interval=0.1): self.name = project.slug self.fpath = os.path.join( - project.doc_path, '%s__rtdlock' % version.slug + project.doc_path, '%s__rtdlock' % version.slug, ) self.timeout = timeout self.polling_interval = polling_interval @@ -95,7 +95,7 @@ class NonBlockingLock: def __init__(self, project, version, max_lock_age=None): self.fpath = os.path.join( - project.doc_path, '%s__rtdlock' % version.slug + project.doc_path, '%s__rtdlock' % version.slug, ) self.max_lock_age = max_lock_age self.name = project.slug From 022594f1bba1f5ac08547913e2bacdaef7c8086d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 18:50:45 +0100 Subject: [PATCH 59/65] Do not touch conf.py for testing --- readthedocs/rtd_tests/files/conf.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py index c4c2ded07..11f872849 100644 --- a/readthedocs/rtd_tests/files/conf.py +++ b/readthedocs/rtd_tests/files/conf.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- +from __future__ import division, print_function, unicode_literals + from datetime import datetime from recommonmark.parser import CommonMarkParser - extensions = [] templates_path = ['templates', '_templates', '.templates'] source_suffix = ['.rst', '.md'] source_parsers = { '.md': CommonMarkParser, -} + } master_doc = 'index' project = 'Pip' copyright = str(datetime.now().year) @@ -22,8 +23,6 @@ htmlhelp_basename = 'pip' html_theme = 'sphinx_rtd_theme' file_insertion_enabled = False latex_documents = [ - ( - 'index', 'pip.tex', 'Pip Documentation', - '', 'manual', - ), + ('index', 'pip.tex', 'Pip Documentation', + '', 'manual'), ] From 91a205e5ab94ccd20c0bb8bc85f78a1c7798a3b6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 19:00:50 +0100 Subject: [PATCH 60/65] Last pre-commit pass --- readthedocs/builds/admin.py | 23 +- readthedocs/builds/models.py | 75 +++- readthedocs/builds/views.py | 3 +- readthedocs/config/config.py | 16 +- readthedocs/core/context_processors.py | 7 +- .../commands/import_github_language.py | 4 +- .../core/management/commands/set_metadata.py | 4 +- readthedocs/core/models.py | 3 +- readthedocs/core/resolver.py | 4 +- readthedocs/core/symlink.py | 9 +- readthedocs/core/templatetags/privacy_tags.py | 3 +- readthedocs/core/urls/single_version.py | 3 +- readthedocs/core/urls/subdomain.py | 17 +- readthedocs/core/views/hooks.py | 27 +- readthedocs/core/views/serve.py | 6 +- readthedocs/doc_builder/backends/mkdocs.py | 11 +- readthedocs/doc_builder/environments.py | 12 +- readthedocs/doc_builder/exceptions.py | 3 +- readthedocs/gold/forms.py | 3 +- readthedocs/gold/urls.py | 4 +- readthedocs/oauth/models.py | 20 +- readthedocs/projects/admin.py | 29 +- readthedocs/projects/forms.py | 36 +- readthedocs/projects/models.py | 344 ++++++++++++------ readthedocs/projects/tasks.py | 24 +- readthedocs/projects/urls/private.py | 63 ++-- readthedocs/projects/views/private.py | 4 +- readthedocs/projects/views/public.py | 7 +- readthedocs/restapi/views/core_views.py | 4 +- readthedocs/restapi/views/model_views.py | 3 +- readthedocs/search/indexes.py | 23 +- readthedocs/search/lib.py | 6 +- readthedocs/search/parse_json.py | 6 +- readthedocs/search/utils.py | 9 +- readthedocs/urls.py | 7 +- readthedocs/vcs_support/backends/hg.py | 5 +- readthedocs/vcs_support/utils.py | 6 +- 37 files changed, 574 insertions(+), 259 deletions(-) diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py index fbd83d8e3..571536f03 100644 --- a/readthedocs/builds/admin.py +++ b/readthedocs/builds/admin.py @@ -15,11 +15,23 @@ class BuildCommandResultInline(admin.TabularInline): class BuildAdmin(admin.ModelAdmin): fields = ( - 'project', 'version', 'type', 'state', 'error', 'success', 'length', + 'project', + 'version', + 'type', + 'state', + 'error', + 'success', + 'length', 'cold_storage', ) list_display = ( - 'id', 'project', 'version_name', 'success', 'type', 'state', 'date', + 'id', + 'project', + 'version_name', + 'success', + 'type', + 'state', + 'date', ) list_filter = ('type', 'state', 'success') list_select_related = ('project', 'version') @@ -34,7 +46,12 @@ class BuildAdmin(admin.ModelAdmin): class VersionAdmin(GuardedModelAdmin): search_fields = ('slug', 'project__name') list_display = ( - 'slug', 'type', 'project', 'privacy_level', 'active', 'built', + 'slug', + 'type', + 'project', + 'privacy_level', + 'active', + 'built', ) list_filter = ('type', 'privacy_level', 'active', 'built') raw_id_fields = ('project',) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 2f8eeae8b..e44d426b3 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -50,7 +50,9 @@ from .version_slug import VersionSlugField DEFAULT_VERSION_PRIVACY_LEVEL = getattr( - settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public', + settings, + 'DEFAULT_VERSION_PRIVACY_LEVEL', + 'public', ) log = logging.getLogger(__name__) @@ -91,7 +93,9 @@ class Version(models.Model): #: filesystem to determine how the paths for this version are called. It #: must not be used for any other identifying purposes. slug = VersionSlugField( - _('Slug'), max_length=255, populate_from='verbose_name', + _('Slug'), + max_length=255, + populate_from='verbose_name', ) supported = models.BooleanField(_('Supported'), default=True) @@ -199,7 +203,8 @@ class Version(models.Model): ) private = self.privacy_level == PRIVATE return self.project.get_docs_url( - version_slug=self.slug, private=private, + version_slug=self.slug, + private=private, ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -209,7 +214,9 @@ class Version(models.Model): for owner in self.project.users.all(): assign('view_version', owner, self) broadcast( - type='app', task=tasks.symlink_project, args=[self.project.pk], + type='app', + task=tasks.symlink_project, + args=[self.project.pk], ) return obj @@ -256,22 +263,26 @@ class Version(models.Model): data['PDF'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['HTML'] = project.get_production_media_url( - 'htmlzip', self.slug, + 'htmlzip', + self.slug, ) if project.has_epub(self.slug): data['Epub'] = project.get_production_media_url( - 'epub', self.slug, + 'epub', + self.slug, ) else: if project.has_pdf(self.slug): data['pdf'] = project.get_production_media_url('pdf', self.slug) if project.has_htmlzip(self.slug): data['htmlzip'] = project.get_production_media_url( - 'htmlzip', self.slug, + 'htmlzip', + self.slug, ) if project.has_epub(self.slug): data['epub'] = project.get_production_media_url( - 'epub', self.slug, + 'epub', + self.slug, ) return data @@ -321,7 +332,11 @@ class Version(models.Model): log.exception('Build path cleanup failed') def get_github_url( - self, docroot, filename, source_suffix='.rst', action='view', + self, + docroot, + filename, + source_suffix='.rst', + action='view', ): """ Return a GitHub URL for a given filename. @@ -364,7 +379,11 @@ class Version(models.Model): ) def get_gitlab_url( - self, docroot, filename, source_suffix='.rst', action='view', + self, + docroot, + filename, + source_suffix='.rst', + action='view', ): repo_url = self.project.repo if 'gitlab' not in repo_url: @@ -462,16 +481,27 @@ class Build(models.Model): """Build data.""" project = models.ForeignKey( - Project, verbose_name=_('Project'), related_name='builds', + Project, + verbose_name=_('Project'), + related_name='builds', ) version = models.ForeignKey( - Version, verbose_name=_('Version'), null=True, related_name='builds', + Version, + verbose_name=_('Version'), + null=True, + related_name='builds', ) type = models.CharField( - _('Type'), max_length=55, choices=BUILD_TYPES, default='html', + _('Type'), + max_length=55, + choices=BUILD_TYPES, + default='html', ) state = models.CharField( - _('State'), max_length=55, choices=BUILD_STATE, default='finished', + _('State'), + max_length=55, + choices=BUILD_STATE, + default='finished', ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) @@ -482,18 +512,25 @@ class Build(models.Model): error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( - _('Commit'), max_length=255, null=True, blank=True, + _('Commit'), + max_length=255, + null=True, + blank=True, ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( - _('Builder'), max_length=255, null=True, blank=True, + _('Builder'), + max_length=255, + null=True, + blank=True, ) cold_storage = models.NullBooleanField( - _('Cold Storage'), help_text='Build steps stored outside the database.', + _('Cold Storage'), + help_text='Build steps stored outside the database.', ) # Manager @@ -624,7 +661,9 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model): """Build command for a ``Build``.""" build = models.ForeignKey( - Build, verbose_name=_('Build'), related_name='commands', + Build, + verbose_name=_('Build'), + related_name='commands', ) command = models.TextField(_('Command')) diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py index 00f08ab2d..e86a673dc 100644 --- a/readthedocs/builds/views.py +++ b/readthedocs/builds/views.py @@ -59,7 +59,8 @@ class BuildTriggerMixin: ) update_docs_task, build = trigger_build( - project=project, version=version, + project=project, + version=version, ) if (update_docs_task, build) == (None, None): # Build was skipped diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 50eba8b3e..e493d09db 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -342,7 +342,8 @@ class BuildConfigV1(BuildConfigBase): if ':' not in build['image']: # Prepend proper image name to user's image name build['image'] = '{}:{}'.format( - DOCKER_DEFAULT_IMAGE, build['image'], + DOCKER_DEFAULT_IMAGE, + build['image'], ) # Update docker default settings from image name if build['image'] in DOCKER_IMAGE_SETTINGS: @@ -371,7 +372,9 @@ class BuildConfigV1(BuildConfigBase): raw_python = self.raw_config['python'] if not isinstance(raw_python, dict): self.error( - 'python', self.PYTHON_INVALID_MESSAGE, code=PYTHON_INVALID, + 'python', + self.PYTHON_INVALID_MESSAGE, + code=PYTHON_INVALID, ) # Validate use_system_site_packages. @@ -445,7 +448,8 @@ class BuildConfigV1(BuildConfigBase): if 'file' in raw_conda: with self.catch_validation_error('conda.file'): conda_environment = validate_file( - raw_conda['file'], self.base_path, + raw_conda['file'], + self.base_path, ) conda['environment'] = conda_environment @@ -683,7 +687,8 @@ class BuildConfigV2(BuildConfigBase): with self.catch_validation_error('python.extra_requirements'): extra_requirements = self.pop_config( - 'python.extra_requirements', [], + 'python.extra_requirements', + [], ) extra_requirements = validate_list(extra_requirements) if extra_requirements and not python['install_with_pip']: @@ -801,7 +806,8 @@ class BuildConfigV2(BuildConfigBase): if not configuration: configuration = None configuration = self.pop_config( - 'sphinx.configuration', configuration, + 'sphinx.configuration', + configuration, ) if configuration is not None: configuration = validate_file(configuration, self.base_path) diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py index 467110e98..8bddb9bd1 100644 --- a/readthedocs/core/context_processors.py +++ b/readthedocs/core/context_processors.py @@ -13,12 +13,15 @@ def readthedocs_processor(request): 'USE_SUBDOMAINS': getattr(settings, 'USE_SUBDOMAINS', None), 'GLOBAL_ANALYTICS_CODE': getattr(settings, 'GLOBAL_ANALYTICS_CODE'), 'DASHBOARD_ANALYTICS_CODE': getattr( - settings, 'DASHBOARD_ANALYTICS_CODE', + settings, + 'DASHBOARD_ANALYTICS_CODE', ), 'SITE_ROOT': getattr(settings, 'SITE_ROOT', '') + '/', 'TEMPLATE_ROOT': getattr(settings, 'TEMPLATE_ROOT', '') + '/', 'DO_NOT_TRACK_ENABLED': getattr( - settings, 'DO_NOT_TRACK_ENABLED', False, + settings, + 'DO_NOT_TRACK_ENABLED', + False, ), 'USE_PROMOS': getattr(settings, 'USE_PROMOS', False), } diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index f3813f21e..46159bcf6 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -68,7 +68,9 @@ class Command(BaseCommand): if not languages: continue sorted_langs = sorted( - list(languages.items()), key=lambda x: x[1], reverse=True, + list(languages.items()), + key=lambda x: x[1], + reverse=True, ) print('Sorted langs: %s ' % sorted_langs) top_lang = sorted_langs[0][0] diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py index 6fe4bb22e..62384fe0e 100644 --- a/readthedocs/core/management/commands/set_metadata.py +++ b/readthedocs/core/management/commands/set_metadata.py @@ -24,7 +24,9 @@ class Command(BaseCommand): log.info('Generating metadata for %s', p) try: broadcast( - type='app', task=tasks.update_static_metadata, args=[p.pk], + type='app', + task=tasks.update_static_metadata, + args=[p.pk], ) except Exception: log.exception('Build failed for %s', p) diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index 91313ff2a..bee057bdb 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -48,7 +48,8 @@ class UserProfile(models.Model): def get_absolute_url(self): return reverse( - 'profiles_profile_detail', kwargs={'username': self.user.username}, + 'profiles_profile_detail', + kwargs={'username': self.user.username}, ) def get_contribution_details(self): diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index f66910a72..7758226cc 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -246,7 +246,9 @@ class ResolverBase: private = version.privacy_level == PRIVATE except Version.DoesNotExist: private = getattr( - settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC, + settings, + 'DEFAULT_PRIVACY_LEVEL', + PUBLIC, ) == PRIVATE return private diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index 67e0cd47a..6bd19f5dd 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -217,7 +217,8 @@ class Symlink: subprojects.add(rel.alias) for from_slug, to_slug in list(from_to.items()): log_msg = 'Symlinking subproject: {} -> {}'.format( - from_slug, to_slug, + from_slug, + to_slug, ) log.info( constants.LOG_TEMPLATE.format( @@ -378,7 +379,8 @@ class PublicSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'public_web_root') PROJECT_CNAME_ROOT = os.path.join( - settings.SITE_ROOT, 'public_cname_project', + settings.SITE_ROOT, + 'public_cname_project', ) def get_version_queryset(self): @@ -400,7 +402,8 @@ class PrivateSymlinkBase(Symlink): CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_root') WEB_ROOT = os.path.join(settings.SITE_ROOT, 'private_web_root') PROJECT_CNAME_ROOT = os.path.join( - settings.SITE_ROOT, 'private_cname_project', + settings.SITE_ROOT, + 'private_cname_project', ) def get_version_queryset(self): diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index f105a300e..12d29f662 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -19,7 +19,8 @@ def is_admin(user, project): @register.simple_tag(takes_context=True) def get_public_projects(context, user): projects = Project.objects.for_user_and_viewer( - user=user, viewer=context['request'].user, + user=user, + viewer=context['request'].user, ) context['public_projects'] = projects return '' diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py index 545617a55..253cae6a3 100644 --- a/readthedocs/core/urls/single_version.py +++ b/readthedocs/core/urls/single_version.py @@ -37,7 +37,8 @@ groups = [single_version_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): groups.insert( - 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), + 0, + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), ) # Allow `/docs/` URL's when not using subdomains or during local dev diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py index 90f3b1865..4e4a6775e 100644 --- a/readthedocs/core/urls/subdomain.py +++ b/readthedocs/core/urls/subdomain.py @@ -26,12 +26,15 @@ subdomain_urls = [ url( r'^(?:|projects/(?P{project_slug})/)' r'page/(?P.*)$'.format(**pattern_opts), - redirect_page_with_filename, name='docs_detail', + redirect_page_with_filename, + name='docs_detail', ), url( (r'^(?:|projects/(?P{project_slug})/)$').format( **pattern_opts - ), redirect_project_slug, name='redirect_project_slug', + ), + redirect_project_slug, + name='redirect_project_slug', ), url( ( @@ -39,7 +42,9 @@ subdomain_urls = [ r'(?P{lang_slug})/' r'(?P{version_slug})/' r'(?P{filename_slug})$'.format(**pattern_opts) - ), serve_docs, name='docs_detail', + ), + serve_docs, + name='docs_detail', ), ] @@ -48,7 +53,11 @@ groups = [subdomain_urls] # Needed to serve media locally if getattr(settings, 'DEBUG', False): groups.insert( - 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT,), + 0, + static( + settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT, + ), ) urlpatterns = reduce(add, groups) diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 162ff932c..3966ba62b 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Views pertaining to builds.""" import json @@ -67,7 +68,9 @@ def build_branches(project, branch_list): versions = project.versions_from_branch_name(branch) for version in versions: log.info( - '(Branch Build) Processing %s:%s', project.slug, version.slug, + '(Branch Build) Processing %s:%s', + project.slug, + version.slug, ) ret = _build_version(project, version.slug, already_built=to_build) if ret: @@ -92,7 +95,9 @@ def sync_versions(project): try: version_identifier = project.get_default_branch() version = ( - project.versions.filter(identifier=version_identifier,).first() + project.versions.filter( + identifier=version_identifier, + ).first() ) if not version: log.info('Unable to sync from %s version', version_identifier) @@ -116,7 +121,11 @@ def get_project_from_url(url): def log_info(project, msg): log.info( - constants.LOG_TEMPLATE.format(project=project, version='', msg=msg,), + constants.LOG_TEMPLATE.format( + project=project, + version='', + msg=msg, + ), ) @@ -151,7 +160,8 @@ def _build_url(url, projects, branches): for project_slug, built in list(all_built.items()): if built: msg = '(URL Build) Build Started: {} [{}]'.format( - url, ' '.join(built), + url, + ' '.join(built), ) log_info(project_slug, msg=msg) ret += msg @@ -159,7 +169,8 @@ def _build_url(url, projects, branches): for project_slug, not_building in list(all_not_building.items()): if not_building: msg = '(URL Build) Not Building: {} [{}]'.format( - url, ' '.join(not_building), + url, + ' '.join(not_building), ) log_info(project_slug, msg=msg) ret += msg @@ -206,12 +217,14 @@ def github_build(request): # noqa: D205 if repo_projects: log.info( 'GitHub webhook search: url=%s branches=%s', - http_search_url, branches, + http_search_url, + branches, ) ssh_projects = get_project_from_url(ssh_search_url) if ssh_projects: log.info( - 'GitHub webhook search: url=%s branches=%s', ssh_search_url, + 'GitHub webhook search: url=%s branches=%s', + ssh_search_url, branches, ) projects = repo_projects | ssh_projects diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 46a6c8ec9..513451d70 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -161,7 +161,11 @@ def _serve_file(request, filename, basepath): @map_project_slug @map_subproject_slug def serve_docs( - request, project, subproject, lang_slug=None, version_slug=None, + request, + project, + subproject, + lang_slug=None, + version_slug=None, filename='', ): """Map existing proj, lang, version, filename views to the file format.""" diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 8d80ecc35..201bd0393 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -97,7 +97,8 @@ class BaseMkdocs(BaseBuilder): if hasattr(exc, 'problem_mark'): mark = exc.problem_mark note = ' (line %d, column %d)' % ( - mark.line + 1, mark.column + 1, + mark.line + 1, + mark.column + 1, ) raise MkDocsYAMLParseError( 'Your mkdocs.yml could not be loaded, ' @@ -209,12 +210,16 @@ class BaseMkdocs(BaseBuilder): 'docroot': docs_dir, 'source_suffix': '.md', 'api_host': getattr( - settings, 'PUBLIC_API_URL', 'https://readthedocs.org', + settings, + 'PUBLIC_API_URL', + 'https://readthedocs.org', ), 'ad_free': not self.project.show_advertising, 'commit': self.version.project.vcs_repo(self.version.slug).commit, 'global_analytics_code': getattr( - settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1', + settings, + 'GLOBAL_ANALYTICS_CODE', + 'UA-17997319-1', ), 'user_analytics_code': analytics_code, } diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index cb27a88dd..ddf0b471d 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -977,13 +977,11 @@ class DockerBuildEnvironment(BuildEnvironment): _('Build exited due to excessive memory consumption'), ) elif state.get('Error'): - self.failure = BuildEnvironmentError( - ( - _('Build exited due to unknown error: {0}').format( - state.get('Error'), - ) - ), - ) + self.failure = BuildEnvironmentError(( + _('Build exited due to unknown error: {0}').format( + state.get('Error'), + ) + ),) def create_container(self): """Create docker container.""" diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index dff39908d..eaa2858aa 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -11,7 +11,8 @@ class BuildEnvironmentException(Exception): def __init__(self, message=None, **kwargs): self.status_code = kwargs.pop( - 'status_code', None, + 'status_code', + None, ) or self.status_code or 1 message = message or self.get_default_message() super().__init__(message, **kwargs) diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py index b60b58e0b..84c19fba3 100644 --- a/readthedocs/gold/forms.py +++ b/readthedocs/gold/forms.py @@ -120,5 +120,6 @@ class GoldProjectForm(forms.Form): return cleaned_data self.add_error( - None, 'You already have the max number of supported projects.', + None, + 'You already have the max number of supported projects.', ) diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py index 7f679686f..295e5250d 100644 --- a/readthedocs/gold/urls.py +++ b/readthedocs/gold/urls.py @@ -16,7 +16,9 @@ urlpatterns = [ name='gold_subscription', ), url( - r'^cancel/$', views.DeleteGoldSubscription.as_view(), name='gold_cancel', + r'^cancel/$', + views.DeleteGoldSubscription.as_view(), + name='gold_cancel', ), url(r'^projects/$', views.projects, name='gold_projects'), url( diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py index 65522c2bc..40d224df6 100644 --- a/readthedocs/oauth/models.py +++ b/readthedocs/oauth/models.py @@ -189,14 +189,12 @@ class RemoteRepository(models.Model): .filter(Q(repo=self.clone_url) | Q(repo__iendswith=fuzzy_url) | Q(repo__iendswith=fuzzy_url + '.git'))) # yapf: disable - return [ - { - 'id': project.slug, - 'url': reverse( - 'projects_detail', - kwargs={ - 'project_slug': project.slug, - }, - ), - } for project in projects - ] + return [{ + 'id': project.slug, + 'url': reverse( + 'projects_detail', + kwargs={ + 'project_slug': project.slug, + }, + ), + } for project in projects] diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 165cf7491..73914222c 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -118,13 +118,21 @@ class ProjectAdmin(GuardedModelAdmin): prepopulated_fields = {'slug': ('name',)} list_display = ('name', 'slug', 'repo', 'repo_type', 'featured') list_filter = ( - 'repo_type', 'featured', 'privacy_level', 'documentation_type', - 'programming_language', 'feature__feature_id', ProjectOwnerBannedFilter, + 'repo_type', + 'featured', + 'privacy_level', + 'documentation_type', + 'programming_language', + 'feature__feature_id', + ProjectOwnerBannedFilter, ) list_editable = ('featured',) search_fields = ('slug', 'repo') inlines = [ - ProjectRelationshipInline, RedirectInline, VersionInline, DomainInline, + ProjectRelationshipInline, + RedirectInline, + VersionInline, + DomainInline, ] readonly_fields = ('feature_flags',) raw_id_fields = ('users', 'main_language_project') @@ -153,21 +161,23 @@ class ProjectAdmin(GuardedModelAdmin): for project in queryset: if project.users.count() == 1: count = ( - UserProfile.objects - .filter(user__projects=project) - .update(banned=True) + UserProfile.objects.filter(user__projects=project + ).update(banned=True) ) # yapf: disabled total += count else: messages.add_message( - request, messages.ERROR, + request, + messages.ERROR, 'Project has multiple owners: {}'.format(project), ) if total == 0: messages.add_message(request, messages.ERROR, 'No users banned') else: messages.add_message( - request, messages.INFO, 'Banned {} user(s)'.format(total), + request, + messages.INFO, + 'Banned {} user(s)'.format(total), ) ban_owner.short_description = 'Ban project owner' @@ -191,7 +201,8 @@ class ProjectAdmin(GuardedModelAdmin): def get_actions(self, request): actions = super().get_actions(request) actions['delete_selected'] = ( - self.__class__.delete_selected_and_artifacts, 'delete_selected', + self.__class__.delete_selected_and_artifacts, + 'delete_selected', delete_selected.short_description, ) return actions diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 115819060..9c9cd5af1 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -228,13 +228,16 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): default_choice = (None, '-' * 9) all_versions = self.instance.versions.values_list( - 'identifier', 'verbose_name', + 'identifier', + 'verbose_name', ) self.fields['default_branch'].widget = forms.Select( choices=[default_choice] + list(all_versions), ) - active_versions = self.instance.all_active_versions().values_list('slug', 'verbose_name') # yapf: disabled + active_versions = self.instance.all_active_versions().values_list( + 'slug', 'verbose_name' + ) # yapf: disabled self.fields['default_version'].widget = forms.Select( choices=active_versions, ) @@ -252,8 +255,9 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm): class UpdateProjectForm( - ProjectTriggerBuildMixin, ProjectBasicsForm, - ProjectExtraForm, + ProjectTriggerBuildMixin, + ProjectBasicsForm, + ProjectExtraForm, ): class Meta: @@ -459,7 +463,8 @@ def build_versions_form(project): class BaseUploadHTMLForm(forms.Form): content = forms.FileField(label=_('Zip file of HTML')) overwrite = forms.BooleanField( - required=False, label=_('Overwrite existing HTML?'), + required=False, + label=_('Overwrite existing HTML?'), ) def __init__(self, *args, **kwargs): @@ -535,7 +540,8 @@ class EmailHookForm(forms.Form): def clean_email(self): self.email = EmailHook.objects.get_or_create( - email=self.cleaned_data['email'], project=self.project, + email=self.cleaned_data['email'], + project=self.project, )[0] return self.email @@ -554,7 +560,8 @@ class WebHookForm(forms.ModelForm): def save(self, commit=True): self.webhook = WebHook.objects.get_or_create( - url=self.cleaned_data['url'], project=self.project, + url=self.cleaned_data['url'], + project=self.project, )[0] self.project.webhook_notifications.add(self.webhook) return self.project @@ -577,14 +584,13 @@ class TranslationBaseForm(forms.Form): self.fields['project'].choices = self.get_choices() def get_choices(self): - return [ - ( - project.slug, '{project} ({lang})'.format( - project=project.slug, lang=project.get_language_display(), - ), - ) - for project in self.get_translation_queryset().all() - ] + return [( + project.slug, + '{project} ({lang})'.format( + project=project.slug, + lang=project.get_language_display(), + ), + ) for project in self.get_translation_queryset().all()] def clean_project(self): translation_project_slug = self.cleaned_data['project'] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 925b9d477..1fefb7be3 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -53,13 +53,21 @@ class ProjectRelationship(models.Model): """ parent = models.ForeignKey( - 'Project', verbose_name=_('Parent'), related_name='subprojects', + 'Project', + verbose_name=_('Parent'), + related_name='subprojects', ) child = models.ForeignKey( - 'Project', verbose_name=_('Child'), related_name='superprojects', + 'Project', + verbose_name=_('Child'), + related_name='superprojects', ) alias = models.SlugField( - _('Alias'), max_length=255, null=True, blank=True, db_index=False, + _('Alias'), + max_length=255, + null=True, + blank=True, + db_index=False, ) objects = ChildRelatedProjectQuerySet.as_manager() @@ -88,37 +96,47 @@ class Project(models.Model): # Generally from conf.py users = models.ManyToManyField( - User, verbose_name=_('User'), related_name='projects', + User, + verbose_name=_('User'), + related_name='projects', ) # A DNS label can contain up to 63 characters. name = models.CharField(_('Name'), max_length=63) slug = models.SlugField(_('Slug'), max_length=63, unique=True) description = models.TextField( - _('Description'), blank=True, + _('Description'), + blank=True, help_text=_( 'The reStructuredText ' 'description of the project', ), ) repo = models.CharField( - _('Repository URL'), max_length=255, + _('Repository URL'), + max_length=255, validators=[validate_repository_url], help_text=_('Hosted documentation repository URL'), ) repo_type = models.CharField( - _('Repository type'), max_length=10, choices=constants.REPO_CHOICES, + _('Repository type'), + max_length=10, + choices=constants.REPO_CHOICES, default='git', ) project_url = models.URLField( - _('Project homepage'), blank=True, + _('Project homepage'), + blank=True, help_text=_('The project\'s homepage'), ) canonical_url = models.URLField( - _('Canonical URL'), blank=True, + _('Canonical URL'), + blank=True, help_text=_('URL that documentation is expected to serve from'), ) single_version = models.BooleanField( - _('Single version'), default=False, help_text=_( + _('Single version'), + default=False, + help_text=_( 'A single version site has no translations and only your ' '"latest" version, served at the root of the domain. Use ' 'this with caution, only turn it on if you will never ' @@ -126,22 +144,32 @@ class Project(models.Model): ), ) default_version = models.CharField( - _('Default version'), max_length=255, default=LATEST, + _('Default version'), + max_length=255, + default=LATEST, help_text=_('The version of your project that / redirects to'), ) # In default_branch, None means the backend should choose the # appropriate branch. Eg 'master' for git default_branch = models.CharField( - _('Default branch'), max_length=255, default=None, null=True, - blank=True, help_text=_( + _('Default branch'), + max_length=255, + default=None, + null=True, + blank=True, + help_text=_( 'What branch "latest" points to. Leave empty ' 'to use the default value for your VCS (eg. ' 'trunk or master).', ), ) requirements_file = models.CharField( - _('Requirements file'), max_length=255, default=None, null=True, - blank=True, help_text=_( + _('Requirements file'), + max_length=255, + default=None, + null=True, + blank=True, + help_text=_( 'A ' 'pip requirements file needed to build your documentation. ' @@ -149,8 +177,11 @@ class Project(models.Model): ), ) documentation_type = models.CharField( - _('Documentation type'), max_length=20, - choices=constants.DOCUMENTATION_CHOICES, default='sphinx', help_text=_( + _('Documentation type'), + max_length=20, + choices=constants.DOCUMENTATION_CHOICES, + default='sphinx', + help_text=_( 'Type of documentation you are building. More info.', @@ -160,17 +191,27 @@ class Project(models.Model): # Project features cdn_enabled = models.BooleanField(_('CDN Enabled'), default=False) analytics_code = models.CharField( - _('Analytics code'), max_length=50, null=True, blank=True, help_text=_( + _('Analytics code'), + max_length=50, + null=True, + blank=True, + help_text=_( 'Google Analytics Tracking ID ' '(ex. UA-22345342-1). ' 'This may slow down your page loads.', ), ) container_image = models.CharField( - _('Alternative container image'), max_length=64, null=True, blank=True, + _('Alternative container image'), + max_length=64, + null=True, + blank=True, ) container_mem_limit = models.CharField( - _('Container memory limit'), max_length=10, null=True, blank=True, + _('Container memory limit'), + max_length=10, + null=True, + blank=True, help_text=_( 'Memory limit in Docker format ' '-- example: 512m or 1g', @@ -182,10 +223,14 @@ class Project(models.Model): blank=True, ) build_queue = models.CharField( - _('Alternate build queue id'), max_length=32, null=True, blank=True, + _('Alternate build queue id'), + max_length=32, + null=True, + blank=True, ) allow_promos = models.BooleanField( - _('Allow paid advertising'), default=True, + _('Allow paid advertising'), + default=True, help_text=_('If unchecked, users will still see community ads.'), ) ad_free = models.BooleanField( @@ -194,32 +239,42 @@ class Project(models.Model): help_text='If checked, do not show advertising for this project', ) show_version_warning = models.BooleanField( - _('Show version warning'), default=False, + _('Show version warning'), + default=False, help_text=_('Show warning banner in non-stable nor latest versions.'), ) # Sphinx specific build options. enable_epub_build = models.BooleanField( - _('Enable EPUB build'), default=True, help_text=_( + _('Enable EPUB build'), + default=True, + help_text=_( 'Create a EPUB version of your documentation with each build.', ), ) enable_pdf_build = models.BooleanField( - _('Enable PDF build'), default=True, help_text=_( + _('Enable PDF build'), + default=True, + help_text=_( 'Create a PDF version of your documentation with each build.', ), ) # Other model data. path = models.CharField( - _('Path'), max_length=255, editable=False, + _('Path'), + max_length=255, + editable=False, help_text=_( 'The directory where ' 'conf.py lives', ), ) conf_py_file = models.CharField( - _('Python configuration file'), max_length=255, default='', blank=True, + _('Python configuration file'), + max_length=255, + default='', + blank=True, help_text=_( 'Path from project root to conf.py file ' '(ex. docs/conf.py). ' @@ -230,74 +285,103 @@ class Project(models.Model): featured = models.BooleanField(_('Featured'), default=False) skip = models.BooleanField(_('Skip'), default=False) install_project = models.BooleanField( - _('Install Project'), help_text=_( + _('Install Project'), + help_text=_( 'Install your project inside a virtualenv using setup.py ' 'install', - ), default=False, + ), + default=False, ) # This model attribute holds the python interpreter used to create the # virtual environment python_interpreter = models.CharField( - _('Python Interpreter'), max_length=20, - choices=constants.PYTHON_CHOICES, default='python', help_text=_( + _('Python Interpreter'), + max_length=20, + choices=constants.PYTHON_CHOICES, + default='python', + help_text=_( 'The Python interpreter used to create the virtual ' 'environment.', ), ) use_system_packages = models.BooleanField( - _('Use system packages'), help_text=_( + _('Use system packages'), + help_text=_( 'Give the virtual environment access to the global ' 'site-packages dir.', - ), default=False, + ), + default=False, ) privacy_level = models.CharField( - _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, + _('Privacy Level'), + max_length=20, + choices=constants.PRIVACY_CHOICES, default=getattr( - settings, 'DEFAULT_PRIVACY_LEVEL', + settings, + 'DEFAULT_PRIVACY_LEVEL', 'public', - ), help_text=_( - 'Level of privacy that you want on the repository. ' - 'Protected means public but not in listings.', + ), + help_text=_( + 'Level of privacy that you want on the repository. ' + 'Protected means public but not in listings.', ), ) version_privacy_level = models.CharField( - _('Version Privacy Level'), max_length=20, + _('Version Privacy Level'), + max_length=20, choices=constants.PRIVACY_CHOICES, default=getattr( - settings, 'DEFAULT_PRIVACY_LEVEL', + settings, + 'DEFAULT_PRIVACY_LEVEL', 'public', - ), help_text=_( - 'Default level of privacy you want on built ' - 'versions of documentation.', + ), + help_text=_( + 'Default level of privacy you want on built ' + 'versions of documentation.', ), ) # Subprojects related_projects = models.ManyToManyField( - 'self', verbose_name=_('Related projects'), blank=True, - symmetrical=False, through=ProjectRelationship, + 'self', + verbose_name=_('Related projects'), + blank=True, + symmetrical=False, + through=ProjectRelationship, ) # Language bits language = models.CharField( - _('Language'), max_length=20, default='en', help_text=_( + _('Language'), + max_length=20, + default='en', + help_text=_( 'The language the project ' 'documentation is rendered in. ' "Note: this affects your project's URL.", - ), choices=constants.LANGUAGES, + ), + choices=constants.LANGUAGES, ) programming_language = models.CharField( - _('Programming Language'), max_length=20, default='words', help_text=_( + _('Programming Language'), + max_length=20, + default='words', + help_text=_( 'The primary programming language the project is written in.', - ), choices=constants.PROGRAMMING_LANGUAGES, blank=True, + ), + choices=constants.PROGRAMMING_LANGUAGES, + blank=True, ) # A subproject pointed at its main language, so it can be tracked main_language_project = models.ForeignKey( - 'self', related_name='translations', on_delete=models.SET_NULL, - blank=True, null=True, + 'self', + related_name='translations', + on_delete=models.SET_NULL, + blank=True, + null=True, ) has_valid_webhook = models.BooleanField( @@ -305,7 +389,8 @@ class Project(models.Model): help_text=_('This project has been built with a webhook'), ) has_valid_clone = models.BooleanField( - default=False, help_text=_('This project has been successfully cloned'), + default=False, + help_text=_('This project has been successfully cloned'), ) tags = TaggableManager(blank=True) @@ -393,13 +478,16 @@ class Project(models.Model): Always use http for now, to avoid content warnings. """ return resolve( - project=self, version_slug=version_slug, language=lang_slug, + project=self, + version_slug=version_slug, + language=lang_slug, private=private, ) def get_builds_url(self): return reverse( - 'builds_project_list', kwargs={ + 'builds_project_list', + kwargs={ 'project_slug': self.slug, }, ) @@ -416,12 +504,8 @@ class Project(models.Model): This is used in search result linking """ if getattr(settings, 'DONT_HIT_DB', True): - return [(proj['slug'], proj['canonical_url']) - for proj in ( - api.project(self.pk) - .subprojects() - .get()['subprojects'] - )] + return [(proj['slug'], proj['canonical_url']) for proj in + (api.project(self.pk).subprojects().get()['subprojects'])] return [(proj.child.slug, proj.child.get_docs_url()) for proj in self.subprojects.all()] @@ -436,17 +520,25 @@ class Project(models.Model): :returns: Full path to media file or path """ - if getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public') == 'public' or settings.DEBUG: + if getattr(settings, 'DEFAULT_PRIVACY_LEVEL', + 'public') == 'public' or settings.DEBUG: path = os.path.join( - settings.MEDIA_ROOT, type_, self.slug, version_slug, + settings.MEDIA_ROOT, + type_, + self.slug, + version_slug, ) else: path = os.path.join( - settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug, + settings.PRODUCTION_MEDIA_ARTIFACTS, + type_, + self.slug, + version_slug, ) if include_file: path = os.path.join( - path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')), + path, + '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')), ) return path @@ -454,7 +546,8 @@ class Project(models.Model): """Get the URL for downloading a specific media file.""" try: path = reverse( - 'project_download_media', kwargs={ + 'project_download_media', + kwargs={ 'project_slug': self.slug, 'type_': type_, 'version_slug': version_slug, @@ -473,13 +566,16 @@ class Project(models.Model): def get_downloads(self): downloads = {} downloads['htmlzip'] = self.get_production_media_url( - 'htmlzip', self.get_default_version(), + 'htmlzip', + self.get_default_version(), ) downloads['epub'] = self.get_production_media_url( - 'epub', self.get_default_version(), + 'epub', + self.get_default_version(), ) downloads['pdf'] = self.get_production_media_url( - 'pdf', self.get_default_version(), + 'pdf', + self.get_default_version(), ) return downloads @@ -634,21 +730,30 @@ class Project(models.Model): def has_pdf(self, version_slug=LATEST): if not self.enable_pdf_build: return False - return os.path.exists(self.get_production_media_path( - type_='pdf', version_slug=version_slug, - )) + return os.path.exists( + self.get_production_media_path( + type_='pdf', + version_slug=version_slug, + ) + ) def has_epub(self, version_slug=LATEST): if not self.enable_epub_build: return False - return os.path.exists(self.get_production_media_path( - type_='epub', version_slug=version_slug, - )) + return os.path.exists( + self.get_production_media_path( + type_='epub', + version_slug=version_slug, + ) + ) def has_htmlzip(self, version_slug=LATEST): - return os.path.exists(self.get_production_media_path( - type_='htmlzip', version_slug=version_slug, - )) + return os.path.exists( + self.get_production_media_path( + type_='htmlzip', + version_slug=version_slug, + ) + ) @property def sponsored(self): @@ -797,7 +902,8 @@ class Project(models.Model): if identifier_updated and current_stable.active and current_stable.machine: log.info( 'Update stable version: {project}:{version}'.format( - project=self.slug, version=new_stable.identifier, + project=self.slug, + version=new_stable.identifier, ), ) current_stable.identifier = new_stable.identifier @@ -809,7 +915,8 @@ class Project(models.Model): .format(project=self.slug, version=new_stable.identifier), ) current_stable = self.versions.create_stable( - type=new_stable.type, identifier=new_stable.identifier, + type=new_stable.type, + identifier=new_stable.identifier, ) return new_stable @@ -833,7 +940,8 @@ class Project(models.Model): return self.default_version # check if the default_version exists version_qs = self.versions.filter( - slug=self.default_version, active=True, + slug=self.default_version, + active=True, ) if version_qs.exists(): return self.default_version @@ -933,9 +1041,12 @@ class APIProject(Project): # These fields only exist on the API return, not on the model, so we'll # remove them to avoid throwing exceptions due to unexpected fields for key in [ - 'users', 'resource_uri', 'absolute_url', 'downloads', - 'main_language_project', 'related_projects', - ]: + 'users', + 'resource_uri', + 'absolute_url', + 'downloads', + 'main_language_project', + 'related_projects',]: try: del kwargs[key] except KeyError: @@ -973,11 +1084,15 @@ class ImportedFile(models.Model): """ project = models.ForeignKey( - 'Project', verbose_name=_('Project'), related_name='imported_files', + 'Project', + verbose_name=_('Project'), + related_name='imported_files', ) version = models.ForeignKey( - 'builds.Version', verbose_name=_('Version'), - related_name='imported_files', null=True, + 'builds.Version', + verbose_name=_('Version'), + related_name='imported_files', + null=True, ) name = models.CharField(_('Name'), max_length=255) slug = models.SlugField(_('Slug')) @@ -988,7 +1103,8 @@ class ImportedFile(models.Model): def get_absolute_url(self): return resolve( - project=self.project, version_slug=self.version.slug, + project=self.project, + version_slug=self.version.slug, filename=self.path, ) @@ -1015,7 +1131,9 @@ class EmailHook(Notification): @python_2_unicode_compatible class WebHook(Notification): url = models.URLField( - max_length=600, blank=True, help_text=_('URL to send the webhook to'), + max_length=600, + blank=True, + help_text=_('URL to send the webhook to'), ) def __str__(self): @@ -1029,23 +1147,29 @@ class Domain(models.Model): project = models.ForeignKey(Project, related_name='domains') domain = models.CharField( - _('Domain'), unique=True, max_length=255, + _('Domain'), + unique=True, + max_length=255, validators=[validate_domain_name], ) machine = models.BooleanField( - default=False, help_text=_('This Domain was auto-created'), + default=False, + help_text=_('This Domain was auto-created'), ) cname = models.BooleanField( - default=False, help_text=_('This Domain is a CNAME for the project'), + default=False, + help_text=_('This Domain is a CNAME for the project'), ) canonical = models.BooleanField( - default=False, help_text=_( + default=False, + help_text=_( 'This Domain is the primary one where the documentation is ' 'served from', ), ) https = models.BooleanField( - _('Use HTTPS'), default=False, + _('Use HTTPS'), + default=False, help_text=_('Always use HTTPS for this domain'), ) count = models.IntegerField( @@ -1060,7 +1184,8 @@ class Domain(models.Model): def __str__(self): return '{domain} pointed at {project}'.format( - domain=self.domain, project=self.project.name, + domain=self.domain, + project=self.project.name, ) def save(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -1124,24 +1249,31 @@ class Feature(models.Model): (PIP_ALWAYS_UPGRADE, _('Always run pip install --upgrade')), (SKIP_SUBMODULES, _('Skip git submodule checkout')), ( - DONT_OVERWRITE_SPHINX_CONTEXT, _( - 'Do not overwrite context vars in conf.py with Read the Docs context', + DONT_OVERWRITE_SPHINX_CONTEXT, + _( + 'Do not overwrite context vars in conf.py with Read the Docs context', ), ), ( - ALLOW_V2_CONFIG_FILE, _( - 'Allow to use the v2 of the configuration file', - ), - ), - (MKDOCS_THEME_RTD, _('Use Read the Docs theme for MkDocs as default theme')), - ( - DONT_SHALLOW_CLONE, _( - 'Do not shallow clone when cloning git repos', + ALLOW_V2_CONFIG_FILE, + _( + 'Allow to use the v2 of the configuration file', ), ), ( - USE_TESTING_BUILD_IMAGE, _( - 'Use Docker image labelled as `testing` to build the docs', + MKDOCS_THEME_RTD, + _('Use Read the Docs theme for MkDocs as default theme') + ), + ( + DONT_SHALLOW_CLONE, + _( + 'Do not shallow clone when cloning git repos', + ), + ), + ( + USE_TESTING_BUILD_IMAGE, + _( + 'Use Docker image labelled as `testing` to build the docs', ), ), ) diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 640a08c9c..5f00374ae 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -161,20 +161,16 @@ class SyncRepositoryMixin: version_post_data = {'repo': version_repo.repo_url} if version_repo.supports_tags: - version_post_data['tags'] = [ - { - 'identifier': v.identifier, - 'verbose_name': v.verbose_name, - } for v in version_repo.tags - ] + version_post_data['tags'] = [{ + 'identifier': v.identifier, + 'verbose_name': v.verbose_name, + } for v in version_repo.tags] if version_repo.supports_branches: - version_post_data['branches'] = [ - { - 'identifier': v.identifier, - 'verbose_name': v.verbose_name, - } for v in version_repo.branches - ] + version_post_data['branches'] = [{ + 'identifier': v.identifier, + 'verbose_name': v.verbose_name, + } for v in version_repo.branches] self.validate_duplicate_reserved_versions(version_post_data) @@ -1230,8 +1226,8 @@ def send_notifications(version_pk, build_pk): for hook in version.project.webhook_notifications.all(): webhook_notification(version, build, hook.url) for email in version.project.emailhook_notifications.all().values_list( - 'email', - flat=True, + 'email', + flat=True, ): email_notification(version, build, email) diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index c1dd9b20c..75d6324d0 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + """Project URLs for authenticated users.""" from django.conf.urls import url @@ -112,20 +113,24 @@ urlpatterns = [ domain_urls = [ url( - r'^(?P[-\w]+)/domains/$', DomainList.as_view(), + r'^(?P[-\w]+)/domains/$', + DomainList.as_view(), name='projects_domains', ), url( - r'^(?P[-\w]+)/domains/create/$', DomainCreate.as_view(), + r'^(?P[-\w]+)/domains/create/$', + DomainCreate.as_view(), name='projects_domains_create', ), url( r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$', - DomainUpdate.as_view(), name='projects_domains_edit', + DomainUpdate.as_view(), + name='projects_domains_edit', ), url( r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$', - DomainDelete.as_view(), name='projects_domains_delete', + DomainDelete.as_view(), + name='projects_domains_delete', ), ] @@ -135,12 +140,15 @@ integration_urls = [ url( r'^(?P{project_slug})/integrations/$'.format( **pattern_opts - ), IntegrationList.as_view(), name='projects_integrations', + ), + IntegrationList.as_view(), + name='projects_integrations', ), url( r'^(?P{project_slug})/integrations/sync/$'.format( **pattern_opts - ), IntegrationWebhookSync.as_view(), + ), + IntegrationWebhookSync.as_view(), name='projects_integrations_webhooks_sync', ), url( @@ -148,7 +156,9 @@ integration_urls = [ r'^(?P{project_slug})/integrations/create/$'.format( **pattern_opts ) - ), IntegrationCreate.as_view(), name='projects_integrations_create', + ), + IntegrationCreate.as_view(), + name='projects_integrations_create', ), url( ( @@ -156,15 +166,18 @@ integration_urls = [ r'integrations/(?P{integer_pk})/$'.format( **pattern_opts ) - ), IntegrationDetail.as_view(), name='projects_integrations_detail', + ), + IntegrationDetail.as_view(), + name='projects_integrations_detail', ), url( ( r'^(?P{project_slug})/' r'integrations/(?P{integer_pk})/' r'exchange/(?P[-\w]+)/$'.format(**pattern_opts) - ), IntegrationExchangeDetail.as_view(), - name='projects_integrations_exchanges_detail', + ), + IntegrationExchangeDetail.as_view(), + name='projects_integrations_exchanges_detail', ), url( ( @@ -172,8 +185,9 @@ integration_urls = [ r'integrations/(?P{integer_pk})/sync/$'.format( **pattern_opts ) - ), IntegrationWebhookSync.as_view(), - name='projects_integrations_webhooks_sync', + ), + IntegrationWebhookSync.as_view(), + name='projects_integrations_webhooks_sync', ), url( ( @@ -181,7 +195,9 @@ integration_urls = [ r'integrations/(?P{integer_pk})/delete/$'.format( **pattern_opts ) - ), IntegrationDelete.as_view(), name='projects_integrations_delete', + ), + IntegrationDelete.as_view(), + name='projects_integrations_delete', ), ] @@ -191,7 +207,8 @@ subproject_urls = [ url( r'^(?P{project_slug})/subprojects/$'.format( **pattern_opts - ), private.ProjectRelationshipList.as_view(), + ), + private.ProjectRelationshipList.as_view(), name='projects_subprojects', ), url( @@ -199,8 +216,9 @@ subproject_urls = [ r'^(?P{project_slug})/subprojects/create/$'.format( **pattern_opts ) - ), private.ProjectRelationshipCreate.as_view(), - name='projects_subprojects_create', + ), + private.ProjectRelationshipCreate.as_view(), + name='projects_subprojects_create', ), url( ( @@ -208,8 +226,9 @@ subproject_urls = [ r'subprojects/(?P{project_slug})/edit/$'.format( **pattern_opts ) - ), private.ProjectRelationshipUpdate.as_view(), - name='projects_subprojects_update', + ), + private.ProjectRelationshipUpdate.as_view(), + name='projects_subprojects_update', ), url( ( @@ -217,8 +236,9 @@ subproject_urls = [ r'subprojects/(?P{project_slug})/delete/$'.format( **pattern_opts ) - ), private.ProjectRelationshipDelete.as_view(), - name='projects_subprojects_delete', + ), + private.ProjectRelationshipDelete.as_view(), + name='projects_subprojects_delete', ), ] @@ -227,7 +247,8 @@ urlpatterns += subproject_urls environmentvariable_urls = [ url( r'^(?P[-\w]+)/environmentvariables/$', - EnvironmentVariableList.as_view(), name='projects_environmentvariables', + EnvironmentVariableList.as_view(), + name='projects_environmentvariables', ), url( r'^(?P[-\w]+)/environmentvariables/create/$', diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 41b3acda3..02256089c 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -220,7 +220,9 @@ def project_delete(request, project_slug): if request.method == 'POST': broadcast( - type='app', task=tasks.remove_dirs, args=[(project.doc_path,)], + type='app', + task=tasks.remove_dirs, + args=[(project.doc_path,)], ) project.delete() messages.success(request, _('Project deleted')) diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py index 9256e92e0..e90bbeb3e 100644 --- a/readthedocs/projects/views/public.py +++ b/readthedocs/projects/views/public.py @@ -116,8 +116,11 @@ def project_badge(request, project_slug): """Return a sweet badge for the project.""" style = request.GET.get('style', 'flat') if style not in ( - 'flat', 'plastic', 'flat-square', 'for-the-badge', - 'social', + 'flat', + 'plastic', + 'flat-square', + 'for-the-badge', + 'social', ): style = 'flat' diff --git a/readthedocs/restapi/views/core_views.py b/readthedocs/restapi/views/core_views.py index 578f11232..1e4afdd8a 100644 --- a/readthedocs/restapi/views/core_views.py +++ b/readthedocs/restapi/views/core_views.py @@ -41,6 +41,8 @@ def docurl(request): ) return Response({ 'url': make_document_url( - project=project, version=version.slug, page=doc, + project=project, + version=version.slug, + page=doc, ), }) diff --git a/readthedocs/restapi/views/model_views.py b/readthedocs/restapi/views/model_views.py index f0586e2fa..60a634c7b 100644 --- a/readthedocs/restapi/views/model_views.py +++ b/readthedocs/restapi/views/model_views.py @@ -138,7 +138,8 @@ class ProjectViewSet(UserSelectViewSet): }) @decorators.action( - detail=True, permission_classes=[permissions.IsAdminUser], + detail=True, + permission_classes=[permissions.IsAdminUser], ) def token(self, request, **kwargs): project = get_object_or_404( diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py index f8c88a419..05a2f759a 100644 --- a/readthedocs/search/indexes.py +++ b/readthedocs/search/indexes.py @@ -237,7 +237,8 @@ class ProjectIndex(Index): 'id': {'type': 'long'}, 'name': {'type': 'string', 'analyzer': 'default_icu'}, 'description': { - 'type': 'string', 'analyzer': 'default_icu', + 'type': 'string', + 'analyzer': 'default_icu', }, 'slug': {'type': 'string', 'index': 'not_analyzed'}, 'lang': {'type': 'string', 'index': 'not_analyzed'}, @@ -266,7 +267,14 @@ class ProjectIndex(Index): doc = {} attrs = ( - 'id', 'name', 'slug', 'description', 'lang', 'tags', 'author', 'url', + 'id', + 'name', + 'slug', + 'description', + 'lang', + 'tags', + 'author', + 'url', ) for attr in attrs: doc[attr] = data.get(attr, '') @@ -367,7 +375,8 @@ class SectionIndex(Index): 'type': 'object', 'properties': { 'code': { - 'type': 'string', 'analyzer': 'default_icu', + 'type': 'string', + 'analyzer': 'default_icu', }, }, }, @@ -383,7 +392,13 @@ class SectionIndex(Index): doc = {} attrs = ( - 'id', 'project', 'title', 'page_id', 'version', 'path', 'content', + 'id', + 'project', + 'title', + 'page_id', + 'version', + 'path', + 'content', 'commit', ) for attr in attrs: diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 16297e641..007a95afd 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -54,7 +54,11 @@ def search_project(request, query, language=None): def search_file( - request, query, project_slug=None, version_slug=LATEST, taxonomy=None, + request, + query, + project_slug=None, + version_slug=LATEST, + taxonomy=None, ): """ Search index for files matching query. diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py index 8f99aa012..72e85a999 100644 --- a/readthedocs/search/parse_json.py +++ b/readthedocs/search/parse_json.py @@ -28,9 +28,9 @@ def process_all_json_files(version, build_dir=True): for root, _, files in os.walk(full_path): for filename in fnmatch.filter(files, '*.fjson'): if filename in [ - 'search.fjson', 'genindex.fjson', - 'py-modindex.fjson', - ]: + 'search.fjson', + 'genindex.fjson', + 'py-modindex.fjson',]: continue html_files.append(os.path.join(root, filename)) page_list = [] diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py index 1e50b8414..85c3c5735 100644 --- a/readthedocs/search/utils.py +++ b/readthedocs/search/utils.py @@ -37,10 +37,12 @@ def process_mkdocs_json(version, build_dir=True): relative_path = parse_path_from_file(file_path=filename) html = parse_content_from_file(file_path=filename) headers = parse_headers_from_file( - documentation_type='mkdocs', file_path=filename, + documentation_type='mkdocs', + file_path=filename, ) sections = parse_sections_from_file( - documentation_type='mkdocs', file_path=filename, + documentation_type='mkdocs', + file_path=filename, ) try: title = sections[0]['title'] @@ -137,7 +139,8 @@ def parse_content_from_file(file_path): if not content: log.info( - '(Search Index) Unable to index file: %s, empty file', file_path, + '(Search Index) Unable to index file: %s, empty file', + file_path, ) else: log.debug('(Search Index) %s length: %s', file_path, len(content)) diff --git a/readthedocs/urls.py b/readthedocs/urls.py index cb723c64f..fe2385081 100644 --- a/readthedocs/urls.py +++ b/readthedocs/urls.py @@ -120,7 +120,12 @@ debug_urls += [ # Export URLs groups = [ - basic_urls, rtd_urls, project_urls, api_urls, core_urls, i18n_urls, + basic_urls, + rtd_urls, + project_urls, + api_urls, + core_urls, + i18n_urls, deprecated_urls, ] diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py index 515e74a77..0361bfa46 100644 --- a/readthedocs/vcs_support/backends/hg.py +++ b/readthedocs/vcs_support/backends/hg.py @@ -109,7 +109,10 @@ class Backend(BaseVCS): if not identifier: identifier = 'tip' exit_code, stdout, stderr = self.run( - 'hg', 'update', '--clean', identifier, + 'hg', + 'update', + '--clean', + identifier, ) if exit_code != 0: raise RepositoryError( diff --git a/readthedocs/vcs_support/utils.py b/readthedocs/vcs_support/utils.py index c948d36fd..53dd5b646 100644 --- a/readthedocs/vcs_support/utils.py +++ b/readthedocs/vcs_support/utils.py @@ -29,7 +29,8 @@ class Lock: def __init__(self, project, version, timeout=5, polling_interval=0.1): self.name = project.slug self.fpath = os.path.join( - project.doc_path, '%s__rtdlock' % version.slug, + project.doc_path, + '%s__rtdlock' % version.slug, ) self.timeout = timeout self.polling_interval = polling_interval @@ -95,7 +96,8 @@ class NonBlockingLock: def __init__(self, project, version, max_lock_age=None): self.fpath = os.path.join( - project.doc_path, '%s__rtdlock' % version.slug, + project.doc_path, + '%s__rtdlock' % version.slug, ) self.max_lock_age = max_lock_age self.name = project.slug From 9f11db5347b2684558d2f1466a6ea21e34f04c0f Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 19:24:01 +0100 Subject: [PATCH 61/65] Lint changes --- .../core/management/commands/import_github_language.py | 6 +----- readthedocs/core/views/serve.py | 10 +++++----- readthedocs/projects/forms.py | 7 ++++++- readthedocs/projects/models.py | 2 +- readthedocs/search/parse_json.py | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index 46159bcf6..261e2a679 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -37,11 +37,7 @@ class Command(BaseCommand): print('Invalid GitHub token, exiting') return - for project in Project.objects.filter( - programming_language__in=[ - 'none', '', 'words', - ], - ).filter(repo__contains='github',): + for project in Project.objects.filter(programming_language__in=['none', '', 'words']).filter(repo__contains='github'): user = repo = '' repo_url = project.repo for regex in GITHUB_REGEXS: diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 513451d70..a1f65a11e 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -58,9 +58,9 @@ def map_subproject_slug(view_func): """ @wraps(view_func) - def inner_view( - request, subproject=None, subproject_slug=None, *args, **kwargs - ): # noqa + def inner_view( # noqa + request, subproject=None, subproject_slug=None, *args, **kwargs, + ): if subproject is None and subproject_slug: # Try to fetch by subproject alias first, otherwise we might end up # redirected to an unrelated project. @@ -88,9 +88,9 @@ def map_project_slug(view_func): """ @wraps(view_func) - def inner_view( + def inner_view( # noqa request, project=None, project_slug=None, *args, **kwargs - ): # noqa + ): if project is None: if not project_slug: project_slug = request.slug diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 9c9cd5af1..aae5e9d27 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -616,9 +616,14 @@ class TranslationBaseForm(forms.Form): raise forms.ValidationError( _(msg).format(lang=self.parent.get_language_display()), ) + + # yapf: disable exists_translation = ( - self.parent.translations.filter(language=self.translation.language).exists() # yapf: disabled + self.parent.translations + .filter(language=self.translation.language) + .exists() ) + # yapf: enable if exists_translation: msg = ('This project already has a translation for {lang}.') raise forms.ValidationError( diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 1fefb7be3..f39a54c46 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1046,7 +1046,7 @@ class APIProject(Project): 'absolute_url', 'downloads', 'main_language_project', - 'related_projects',]: + 'related_projects']: try: del kwargs[key] except KeyError: diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py index 72e85a999..194c55ff1 100644 --- a/readthedocs/search/parse_json.py +++ b/readthedocs/search/parse_json.py @@ -30,7 +30,7 @@ def process_all_json_files(version, build_dir=True): if filename in [ 'search.fjson', 'genindex.fjson', - 'py-modindex.fjson',]: + 'py-modindex.fjson']: continue html_files.append(os.path.join(root, filename)) page_list = [] From ed67c549a5c96febac4e614307f131adcf95e856 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 19:29:39 +0100 Subject: [PATCH 62/65] noqa on one line --- readthedocs/core/management/commands/import_github_language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py index 261e2a679..bf99ac265 100644 --- a/readthedocs/core/management/commands/import_github_language.py +++ b/readthedocs/core/management/commands/import_github_language.py @@ -37,7 +37,7 @@ class Command(BaseCommand): print('Invalid GitHub token, exiting') return - for project in Project.objects.filter(programming_language__in=['none', '', 'words']).filter(repo__contains='github'): + for project in Project.objects.filter(programming_language__in=['none', '', 'words']).filter(repo__contains='github'): # noqa user = repo = '' repo_url = project.repo for regex in GITHUB_REGEXS: From 6092d0efaee13a045f9faa94f6a68e9927382942 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 20:35:12 +0100 Subject: [PATCH 63/65] Remove py36 from travis.matrix.include --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e5083ec7d..25bb76d7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ env: - ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz matrix: include: - - python: 3.6 - env: TOXENV=py36 - python: 3.6 env: TOXENV=docs - python: 3.6 From f68d84a2efbf15f22b9839db079deb12358a73da Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 20:38:58 +0100 Subject: [PATCH 64/65] Move ES env vars to py36 job --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25bb76d7b..802c8986c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: python python: - 3.6 -env: - - ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz matrix: include: + - python: 3.6 + env: TOXENV=py36 ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz - python: 3.6 env: TOXENV=docs - python: 3.6 From af867c1d85a157b7106734fe45a0fabde9385083 Mon Sep 17 00:00:00 2001 From: Anthony Date: Mon, 21 Jan 2019 18:20:25 -0700 Subject: [PATCH 65/65] Allow large form posts via multipart encoded forms to command API (#5000) This temporarily puts this under a feature flag, to make debugging a little easier, but initial tests show things working as expected. This allows for a large form post to be chunked up and POST, where we were noticing a number of different issues with body size before. --- readthedocs/doc_builder/environments.py | 21 ++++++++++++++++++++- readthedocs/projects/models.py | 2 ++ readthedocs/restapi/views/model_views.py | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index ddf0b471d..9aebcbfbe 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -17,6 +17,7 @@ from docker import APIClient from docker.errors import APIError as DockerAPIError from docker.errors import DockerException from requests.exceptions import ConnectionError +from requests_toolbelt.multipart.encoder import MultipartEncoder from slumber.exceptions import HttpClientError from readthedocs.builds.constants import BUILD_STATE_FINISHED @@ -266,7 +267,25 @@ class BuildCommand(BuildCommandResultMixin): 'start_time': self.start_time, 'end_time': self.end_time, } - api_v2.command.post(data) + + if self.build_env.project.has_feature(Feature.API_LARGE_DATA): + # Don't use slumber directly here. Slumber tries to enforce a string, + # which will break our multipart encoding here. + encoder = MultipartEncoder( + {key: str(value) for key, value in data.items()} + ) + resource = api_v2.command + resp = resource._store['session'].post( + resource._store['base_url'] + '/', + data=encoder, + headers={ + 'Content-Type': encoder.content_type, + } + ) + log.info('Post response via multipart form: %s', resp) + else: + resp = api_v2.command.post(data) + log.info('Post response via JSON encoded data: %s', resp) class DockerBuildCommand(BuildCommand): diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index f39a54c46..819af935a 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1239,6 +1239,7 @@ class Feature(models.Model): DONT_OVERWRITE_SPHINX_CONTEXT = 'dont_overwrite_sphinx_context' ALLOW_V2_CONFIG_FILE = 'allow_v2_config_file' MKDOCS_THEME_RTD = 'mkdocs_theme_rtd' + API_LARGE_DATA = 'api_large_data' DONT_SHALLOW_CLONE = 'dont_shallow_clone' USE_TESTING_BUILD_IMAGE = 'use_testing_build_image' @@ -1276,6 +1277,7 @@ class Feature(models.Model): 'Use Docker image labelled as `testing` to build the docs', ), ), + (API_LARGE_DATA, _('Try alternative method of posting large data')) ) projects = models.ManyToManyField( diff --git a/readthedocs/restapi/views/model_views.py b/readthedocs/restapi/views/model_views.py index 60a634c7b..9175ca64c 100644 --- a/readthedocs/restapi/views/model_views.py +++ b/readthedocs/restapi/views/model_views.py @@ -8,6 +8,7 @@ from allauth.socialaccount.models import SocialAccount from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from rest_framework import decorators, permissions, status, viewsets +from rest_framework.parsers import MultiPartParser, JSONParser, FormParser from rest_framework.renderers import BaseRenderer, JSONRenderer from rest_framework.response import Response @@ -277,6 +278,7 @@ class BuildViewSet(SettingsOverrideObject): class BuildCommandViewSet(UserSelectViewSet): + parser_classes = [JSONParser, MultiPartParser] permission_classes = [APIRestrictedPermission] renderer_classes = (JSONRenderer,) serializer_class = BuildCommandSerializer

  • Table headerTable header 2