diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 5e44ed78daa..08bb8e7d349 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -59,7 +59,7 @@ def __init__(self, *args, **kwargs): if self.project.has_feature(Feature.MKDOCS_THEME_RTD): self.DEFAULT_THEME_NAME = 'readthedocs' log.warning( - 'Project using readthedocs theme as default for MkDocs.', + "Project using readthedocs theme as default for MkDocs.", project_slug=self.project.slug, ) else: @@ -76,14 +76,14 @@ def get_final_doctype(self): self.yaml_file, "r", allow_symlinks=True, base_path=self.project_path ) as fh: config = yaml_load_safely(fh) - use_directory_urls = config.get('use_directory_urls', True) + use_directory_urls = config.get("use_directory_urls", True) return MKDOCS if use_directory_urls else MKDOCS_HTML def get_yaml_config(self): """Find the ``mkdocs.yml`` file in the project root.""" mkdocs_path = self.config.mkdocs.configuration if not mkdocs_path: - mkdocs_path = 'mkdocs.yml' + mkdocs_path = "mkdocs.yml" return os.path.join( self.project_path, mkdocs_path, @@ -108,25 +108,13 @@ def load_yaml_config(self): config = yaml_load_safely(result) if not config: - raise MkDocsYAMLParseError( - MkDocsYAMLParseError.EMPTY_CONFIG - ) + raise MkDocsYAMLParseError(MkDocsYAMLParseError.EMPTY_CONFIG) if not isinstance(config, dict): - raise MkDocsYAMLParseError( - MkDocsYAMLParseError.CONFIG_NOT_DICT - ) + raise MkDocsYAMLParseError(MkDocsYAMLParseError.CONFIG_NOT_DICT) return config except IOError: - log.info( - 'Creating default MkDocs config file for project.', - project_slug=self.project.slug, - version_slug=self.version.slug, - ) - return { - 'site_name': self.version.project.name, - 'docs_dir': self.docs_dir(), - } + raise MkDocsYAMLParseError(MkDocsYAMLParseError.NOT_FOUND) except yaml.YAMLError as exc: note = '' if hasattr(exc, 'problem_mark'): @@ -150,7 +138,7 @@ def append_conf(self): user_config = self.load_yaml_config() # Handle custom docs dirs - docs_dir = user_config.get('docs_dir', 'docs') + docs_dir = user_config.get("docs_dir", "docs") if not isinstance(docs_dir, (type(None), str)): raise MkDocsYAMLParseError( MkDocsYAMLParseError.INVALID_DOCS_DIR_CONFIG, @@ -167,14 +155,14 @@ def append_conf(self): # Set mkdocs config values. extra_assets = { - 'extra_javascript': [ - 'readthedocs-data.js', - f'{static_url}core/js/readthedocs-doc-embed.js', - f'{static_url}javascript/readthedocs-analytics.js', + "extra_javascript": [ + "readthedocs-data.js", + f"{static_url}core/js/readthedocs-doc-embed.js", + f"{static_url}javascript/readthedocs-analytics.js", ], - 'extra_css': [ - f'{static_url}css/badge_only.css', - f'{static_url}css/readthedocs-doc-embed.css', + "extra_css": [ + f"{static_url}css/badge_only.css", + f"{static_url}css/readthedocs-doc-embed.css", ], } @@ -189,11 +177,7 @@ def append_conf(self): ), ) # Add the static file only if isn't already in the list. - value.extend([ - extra - for extra in extras - if extra not in value - ]) + value.extend([extra for extra in extras if extra not in value]) user_config[config] = value # The docs path is relative to the location @@ -257,26 +241,27 @@ def generate_rtd_data(self, docs_dir, mkdocs_config): self.version.project.vcs_repo( version=self.version.slug, environment=self.build_env, - ) - .commit, + ).commit, ) # Will be available in the JavaScript as READTHEDOCS_DATA. readthedocs_data = { - 'project': self.version.project.slug, - 'version': self.version.slug, - 'language': self.version.project.language, - 'programming_language': self.version.project.programming_language, - 'page': None, - 'theme': self.get_theme_name(mkdocs_config), - 'builder': 'mkdocs', - 'docroot': docs_dir, - 'source_suffix': '.md', - 'api_host': settings.PUBLIC_API_URL, - 'ad_free': not self.project.show_advertising, - 'commit': commit, - 'global_analytics_code': ( - None if self.project.analytics_disabled else settings.GLOBAL_ANALYTICS_CODE + "project": self.version.project.slug, + "version": self.version.slug, + "language": self.version.project.language, + "programming_language": self.version.project.programming_language, + "page": None, + "theme": self.get_theme_name(mkdocs_config), + "builder": "mkdocs", + "docroot": docs_dir, + "source_suffix": ".md", + "api_host": settings.PUBLIC_API_URL, + "ad_free": not self.project.show_advertising, + "commit": commit, + "global_analytics_code": ( + None + if self.project.analytics_disabled + else settings.GLOBAL_ANALYTICS_CODE ), "user_analytics_code": analytics_code, "proxied_static_path": self.project.proxied_static_path, @@ -284,11 +269,11 @@ def generate_rtd_data(self, docs_dir, mkdocs_config): } data_ctx = { - 'readthedocs_data': readthedocs_data, - 'current_version': readthedocs_data['version'], - 'slug': readthedocs_data['project'], - 'html_theme': readthedocs_data['theme'], - 'pagename': None, + "readthedocs_data": readthedocs_data, + "current_version": readthedocs_data["version"], + "slug": readthedocs_data["project"], + "html_theme": readthedocs_data["theme"], + "pagename": None, } tmpl = template_loader.get_template('doc_builder/data.js.tmpl') return tmpl.render(data_ctx) @@ -342,7 +327,6 @@ def get_theme_name(self, mkdocs_config): class MkdocsHTML(BaseMkdocs): - builder = "build" build_dir = "_readthedocs/html" @@ -391,7 +375,9 @@ def represent_name(self, data): return self.represent_scalar("tag:yaml.org,2002:python/name:" + data.value, "") -SafeLoader.add_multi_constructor("tag:yaml.org,2002:python/name:", SafeLoader.construct_python_name) +SafeLoader.add_multi_constructor( + "tag:yaml.org,2002:python/name:", SafeLoader.construct_python_name +) SafeLoader.add_constructor(None, SafeLoader.ignore_unknown) SafeDumper.add_representer(ProxyPythonName, SafeDumper.represent_name) diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index 449347e2160..63b988fb963 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -12,7 +12,6 @@ import structlog from django.conf import settings from django.template import loader as template_loader -from django.template.loader import render_to_string from django.urls import reverse from requests.exceptions import ConnectionError @@ -24,7 +23,6 @@ from readthedocs.projects.exceptions import ProjectConfigurationError, UserFileNotFound from readthedocs.projects.models import Feature from readthedocs.projects.templatetags.projects_tags import sort_version_aware -from readthedocs.projects.utils import safe_write from ..base import BaseBuilder from ..constants import PDF_RE @@ -113,24 +111,6 @@ def __init__(self, *args, **kwargs): # because Read the Docs will automatically create one for it. pass - def _write_config(self, master_doc='index'): - """Create ``conf.py`` if it doesn't exist.""" - log.info( - 'Creating default Sphinx config file for project.', - project_slug=self.project.slug, - version_slug=self.version.slug, - ) - docs_dir = self.docs_dir() - conf_template = render_to_string( - '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) def get_config_params(self): """Get configuration parameters to be rendered into the conf file.""" @@ -191,30 +171,27 @@ def get_config_params(self): version_slug=self.version.slug, ) - build_id = self.build_env.build.get('id') + build_id = self.build_env.build.get("id") build_url = None if build_id: build_url = reverse( - 'builds_detail', + "builds_detail", kwargs={ - 'project_slug': self.project.slug, - 'build_pk': build_id, + "project_slug": self.project.slug, + "build_pk": build_id, }, ) - protocol = 'http' if settings.DEBUG else 'https' - build_url = f'{protocol}://{settings.PRODUCTION_DOMAIN}{build_url}' + protocol = "http" if settings.DEBUG else "https" + build_url = f"{protocol}://{settings.PRODUCTION_DOMAIN}{build_url}" vcs_url = None if self.version.is_external: vcs_url = self.version.vcs_url - commit = ( - self.project.vcs_repo( - version=self.version.slug, - environment=self.build_env, - ) - .commit - ) + commit = self.project.vcs_repo( + version=self.version.slug, + environment=self.build_env, + ).commit data = { "current_version": self.version.verbose_name, @@ -267,34 +244,26 @@ def get_config_params(self): def append_conf(self): """ - Find or create a ``conf.py`` and appends default content. + Find a ``conf.py`` and appends default content. The default content is rendered from ``doc_builder/conf.py.tmpl``. """ + if self.config_file is None: + raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) - # Generate a `conf.py` from a template - # - # TODO: we should remove this feature at some point to move forward - # with the idea of remove magic from the builders. - if not self.config_file: - self._write_config() + self.config_file = self.config_file or self.project.conf_file(self.version.slug) - try: - self.config_file = ( - self.config_file or self.project.conf_file(self.version.slug) + if not os.path.exists(self.config_file): + raise UserFileNotFound( + UserFileNotFound.FILE_NOT_FOUND.format(self.config_file) ) - # Allow symlinks, but only the ones that resolve inside the base directory. - outfile = safe_open( - self.config_file, "a", allow_symlinks=True, base_path=self.project_path - ) - if not outfile: - raise UserFileNotFound( - UserFileNotFound.FILE_NOT_FOUND.format(self.config_file) - ) - except IOError as exc: - raise ProjectConfigurationError( - ProjectConfigurationError.NOT_FOUND - ) from exc + + # Allow symlinks, but only the ones that resolve inside the base directory. + # NOTE: if something goes wrong, + # `safe_open` raises an exception that's clearly communicated to the user. + outfile = safe_open( + self.config_file, "a", allow_symlinks=True, base_path=self.project_path + ) # Append config to project conf file tmpl = template_loader.get_template('doc_builder/conf.py.tmpl') @@ -319,8 +288,8 @@ def build(self): project = self.project build_command = [ *self.get_sphinx_cmd(), - '-T', - '-E', + "-T", + "-E", ] if self.config.sphinx.fail_on_warning: build_command.extend(["-W", "--keep-going"]) @@ -354,9 +323,9 @@ def build(self): def get_sphinx_cmd(self): return ( - self.python_env.venv_bin(filename='python'), - '-m', - 'sphinx', + self.python_env.venv_bin(filename="python"), + "-m", + "sphinx", ) @@ -576,19 +545,19 @@ def _build_latexmk(self, cwd): latex_class = LatexBuildCommand cmd = [ - 'latexmk', - '-r', + "latexmk", + "-r", rcfile, # FIXME: check for platex here as well - '-pdfdvi' if self.project.language == 'ja' else '-pdf', + "-pdfdvi" if self.project.language == "ja" else "-pdf", # When ``-f`` is used, latexmk will continue building if it # encounters errors. We still receive a failure exit code in this # case, but the correct steps should run. - '-f', - '-dvi-', - '-ps-', - f'-jobname={self.project.slug}', - '-interaction=nonstopmode', + "-f", + "-dvi-", + "-ps-", + f"-jobname={self.project.slug}", + "-interaction=nonstopmode", ] cmd_ret = self.build_env.run_command_class( diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py index 51dcaa1ce23..bbf9fc2158a 100644 --- a/readthedocs/doc_builder/base.py +++ b/readthedocs/doc_builder/base.py @@ -5,7 +5,6 @@ import structlog - log = structlog.get_logger(__name__) @@ -53,7 +52,7 @@ def _post_build(self): def docs_dir(self): """Handle creating a custom docs_dir if it doesn't exist.""" - for doc_dir_name in ['docs', 'doc', 'Doc', 'book']: + for doc_dir_name in ["docs", "doc", "Doc", "book"]: possible_path = os.path.join(self.project_path, doc_dir_name) if os.path.exists(possible_path): return possible_path diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 89d16011658..9a4c973d266 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -24,10 +24,10 @@ def get_default_message(self): class BuildAppError(BuildBaseException): GENERIC_WITH_BUILD_ID = gettext_noop( - 'There was a problem with Read the Docs while building your documentation. ' - 'Please try again later. ' - 'If this problem persists, ' - 'report this error to us with your build id ({build_id}).', + "There was a problem with Read the Docs while building your documentation. " + "Please try again later. " + "If this problem persists, " + "report this error to us with your build id ({build_id}).", ) @@ -83,7 +83,7 @@ class BuildUserSkip(BuildUserError): class ProjectBuildsSkippedError(BuildUserError): - message = gettext_noop('Builds for this project are temporarily disabled') + message = gettext_noop("Builds for this project are temporarily disabled") class YAMLParseError(BuildUserError): @@ -93,11 +93,13 @@ class YAMLParseError(BuildUserError): class BuildMaxConcurrencyError(BuildUserError): - message = gettext_noop('Concurrency limit reached ({limit}), retrying in 5 minutes.') + message = gettext_noop( + "Concurrency limit reached ({limit}), retrying in 5 minutes." + ) class BuildCancelled(BuildUserError): - message = gettext_noop('Build cancelled by user.') + message = gettext_noop("Build cancelled by user.") state = BUILD_STATE_CANCELLED @@ -124,17 +126,21 @@ class MkDocsYAMLParseError(BuildUserError): INVALID_EXTRA_CONFIG = gettext_noop( 'The "{config}" config from your MkDocs YAML config file has to be a ' - 'list of relative paths.', + "list of relative paths.", ) EMPTY_CONFIG = gettext_noop( - 'Please make sure the MkDocs YAML configuration file is not empty.', + "Please make sure the MkDocs YAML configuration file is not empty.", + ) + NOT_FOUND = gettext_noop( + "A configuration file was not found. " + 'Make sure you have a "mkdocs.yml" file in your repository.', ) CONFIG_NOT_DICT = gettext_noop( - 'Your MkDocs YAML config file is incorrect. ' - 'Please follow the user guide https://www.mkdocs.org/user-guide/configuration/ ' - 'to configure the file properly.', + "Your MkDocs YAML config file is incorrect. " + "Please follow the user guide https://www.mkdocs.org/user-guide/configuration/ " + "to configure the file properly.", ) diff --git a/readthedocs/projects/utils.py b/readthedocs/projects/utils.py index ae870b42556..248b537a3e8 100644 --- a/readthedocs/projects/utils.py +++ b/readthedocs/projects/utils.py @@ -1,36 +1,13 @@ """Utility functions used by projects.""" import csv -import os import structlog from django.http import StreamingHttpResponse -from readthedocs.core.utils.filesystem import safe_open - log = structlog.get_logger(__name__) -def safe_write(filename, contents): - """ - Normalize and write to filename. - - Write ``contents`` to the given ``filename``. If the filename's - directory does not exist, it is created. Contents are written as UTF-8, - ignoring any characters that cannot be encoded as UTF-8. - - :param filename: Filename to write to - :param contents: File contents to write to file - """ - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname) - - with safe_open(filename, "w", encoding="utf-8", errors="ignore") as fh: - fh.write(contents) - fh.close() - - class Echo: """ @@ -53,5 +30,5 @@ def get_csv_file(filename, csv_data): (writer.writerow(row) for row in csv_data), content_type="text/csv", ) - response['Content-Disposition'] = f'attachment; filename="{filename}"' + response["Content-Disposition"] = f'attachment; filename="{filename}"' return response diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index b24b0807a00..2c6d87f36ae 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -10,7 +10,6 @@ from django.test.utils import override_settings from django_dynamic_fixture import get -from readthedocs.builds.constants import EXTERNAL from readthedocs.builds.models import Version from readthedocs.doc_builder.backends.mkdocs import ( MkdocsHTML, @@ -27,16 +26,14 @@ from readthedocs.doc_builder.environments import LocalBuildEnvironment from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError from readthedocs.doc_builder.python_environments import Virtualenv -from readthedocs.projects.constants import PRIVATE, PUBLIC from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Feature, Project from readthedocs.rtd_tests.tests.test_config_integration import create_load -@override_settings(PRODUCTION_DOMAIN='readthedocs.org') +@override_settings(PRODUCTION_DOMAIN="readthedocs.org") class SphinxBuilderTest(TestCase): - - fixtures = ['test_data', 'eric'] + fixtures = ["test_data", "eric"] def setUp(self): self.project = Project.objects.get(slug='pip') @@ -46,7 +43,7 @@ def setUp(self): self.build_env.project = self.project self.build_env.version = self.version self.build_env.build = { - 'id': 123, + "id": 123, } self.build_env.api_client = mock.MagicMock() @@ -86,91 +83,12 @@ def test_conf_py_path(self, checkout_path, docs_dir): expected, ) - @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') - @patch('readthedocs.projects.models.Project.checkout_path') - def test_conf_py_external_version(self, checkout_path, docs_dir): - self.version.type = EXTERNAL - self.version.verbose_name = '123' - self.version.save() - - tmp_dir = tempfile.mkdtemp() - checkout_path.return_value = tmp_dir - docs_dir.return_value = tmp_dir - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=None, - ) - base_sphinx = BaseSphinx( - build_env=self.build_env, - python_env=python_env, - ) - - base_sphinx.config_file = os.path.join(tmp_dir, 'config.py') - params = base_sphinx.get_config_params() - self.assertEqual(params['current_version'], '123') - self.assertEqual(params['version'], self.version) - self.assertEqual(params['build_url'], 'https://readthedocs.org/projects/pip/builds/123/') - - @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') - @patch('readthedocs.builds.models.Version.get_conf_py_path') - @patch('readthedocs.projects.models.Project.checkout_path') - def test_html_context_only_has_public_versions( - self, - checkout_path, - get_conf_py_path, - docs_dir, - ): - tmp_dir = tempfile.mkdtemp() - checkout_path.return_value = tmp_dir - docs_dir.return_value = tmp_dir - get_conf_py_path.side_effect = ProjectConfigurationError - - self.build_env.api_client.version().get.return_value = {"downloads": []} - self.build_env.api_client.project().active_versions.get.return_value = { - 'versions': [ - { - 'slug': 'v1', - 'privacy_level': PUBLIC, - }, - { - 'slug': 'v2', - 'privacy_level': PUBLIC, - }, - { - 'slug': 'v3', - 'privacy_level': PRIVATE, - }, - { - 'slug': 'latest', - 'privacy_level': PRIVATE, - }, - ], - } - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=None, - ) - base_sphinx = BaseSphinx( - build_env=self.build_env, - python_env=python_env, - ) - base_sphinx.config_file = tempfile.mktemp() - context = base_sphinx.get_config_params() - versions = { - v.slug - for v in context['versions'] - } - self.assertEqual(versions, {'v1', 'v2'}) - @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.get_config_params') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.run') @patch('readthedocs.builds.models.Version.get_conf_py_path') @patch('readthedocs.projects.models.Project.checkout_path') - def test_create_conf_py( + def test_project_without_conf_py( self, checkout_path, get_conf_py_path, @@ -182,12 +100,7 @@ def test_create_conf_py( Test for a project without ``conf.py`` file. When this happen, the ``get_conf_py_path`` raises a - ``ProjectConfigurationError`` which is captured by our own code and - generates a conf.py file based using our own template. - - This template should be properly rendered in Python2 and Python3 without - any kind of exception raised by ``append_conf`` (we were originally - having a ``TypeError`` because of an encoding problem in Python3) + ``ProjectConfigurationError`` which is captured by our own code. """ tmp_dir = tempfile.mkdtemp() checkout_path.return_value = tmp_dir @@ -203,27 +116,10 @@ def test_create_conf_py( build_env=self.build_env, python_env=python_env, ) - try: - with override_settings(DOCROOT=tmp_dir): - base_sphinx.append_conf() - except Exception: - pytest.fail('Exception was generated when append_conf called.') - - # Check the content generated by our method is the same than what we - # expects from a pre-generated file - generated_conf_py = os.path.join(base_sphinx.docs_dir(), 'conf.py') - expected_conf_py = os.path.join( - os.path.dirname(__file__), - '..', - 'files', - '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], - ) + with pytest.raises( + ProjectConfigurationError, match=ProjectConfigurationError.NOT_FOUND + ): + base_sphinx.append_conf() @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.docs_dir') @patch('readthedocs.doc_builder.backends.sphinx.BaseSphinx.get_config_params') @@ -231,12 +127,7 @@ def test_create_conf_py( @patch('readthedocs.builds.models.Version.get_conf_py_path') @patch('readthedocs.projects.models.Project.checkout_path') def test_multiple_conf_py( - self, - checkout_path, - get_conf_py_path, - _, - get_config_params, - docs_dir, + self, checkout_path, get_conf_py_path, _, get_config_params, docs_dir ): """ Test for a project with multiple ``conf.py`` files. @@ -265,9 +156,9 @@ def test_multiple_conf_py( with override_settings(DOCROOT=tmp_docs_dir): base_sphinx.append_conf() - @mock.patch('readthedocs.doc_builder.config.load_config') + @mock.patch("readthedocs.doc_builder.config.load_config") def test_use_sphinx_builders(self, load_config): - config_data = {'version': 2, 'sphinx': {'configuration': 'docs/conf.py'}} + config_data = {"version": 2, "sphinx": {"configuration": "docs/conf.py"}} load_config.side_effect = create_load(config_data) config = load_yaml_config(self.version) @@ -280,19 +171,19 @@ def test_use_sphinx_builders(self, load_config): build_env=self.build_env, python_env=python_env, ) - self.assertEqual(builder.sphinx_builder, 'html') + self.assertEqual(builder.sphinx_builder, "html") builder = HtmlDirBuilder( build_env=self.build_env, python_env=python_env, ) - self.assertEqual(builder.sphinx_builder, 'dirhtml') + self.assertEqual(builder.sphinx_builder, "dirhtml") builder = SingleHtmlBuilder( build_env=self.build_env, python_env=python_env, ) - self.assertEqual(builder.sphinx_builder, 'singlehtml') + self.assertEqual(builder.sphinx_builder, "singlehtml") @override_settings(PRODUCTION_DOMAIN='readthedocs.org') @@ -372,8 +263,8 @@ def test_get_theme_name_with_feature_flag(self, checkout_path, run): with patch('readthedocs.doc_builder.backends.mkdocs.yaml') as mock_yaml: with patch('readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.load_yaml_config') as mock_load_yaml_config: mock_load_yaml_config.return_value = { - 'site_name': self.project.name, - 'docs_dir': tmpdir, + "site_name": self.project.name, + "docs_dir": tmpdir, } with override_settings(DOCROOT=tmpdir): builder.append_conf() @@ -398,9 +289,9 @@ def test_get_theme_name_with_feature_flag(self, checkout_path, run): self.assertEqual(builder.get_theme_name(config), 'customtheme') with patch('readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.load_yaml_config') as mock_load_yaml_config: mock_load_yaml_config.return_value = { - 'site_name': self.project.name, - 'theme': 'customtheme', - 'docs_dir': tmpdir, + "site_name": self.project.name, + "theme": "customtheme", + "docs_dir": tmpdir, } with override_settings(DOCROOT=tmpdir): builder.append_conf() @@ -418,58 +309,6 @@ def test_get_theme_name_with_feature_flag(self, checkout_path, run): Dumper=SafeDumper, ) - @patch('readthedocs.doc_builder.base.BaseBuilder.run') - @patch('readthedocs.projects.models.Project.checkout_path') - def test_append_conf_create_yaml(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, 'docs')) - 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, - ) - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY) - - # There is a mkdocs.yml file created - generated_yaml = os.path.join(tmpdir, 'mkdocs.yml') - self.assertTrue(os.path.exists(generated_yaml)) - config = yaml_load_safely(open(generated_yaml)) - self.assertEqual( - config['docs_dir'], - os.path.join(tmpdir, 'docs'), - ) - self.assertEqual( - config['extra_css'], - [ - "/_/static/css/badge_only.css", - "/_/static/css/readthedocs-doc-embed.css", - ], - ) - self.assertEqual( - config['extra_javascript'], - [ - "readthedocs-data.js", - "/_/static/core/js/readthedocs-doc-embed.js", - "/_/static/javascript/readthedocs-analytics.js", - ], - ) - self.assertIsNone( - config['google_analytics'], - ) - self.assertEqual( - config['site_name'], - 'mkdocs', - ) - @patch('readthedocs.doc_builder.base.BaseBuilder.run') @patch('readthedocs.projects.models.Project.checkout_path') def test_append_conf_existing_yaml_on_root(self, checkout_path, run): @@ -611,9 +450,9 @@ def test_append_conf_existing_yaml_on_root_with_invalid_setting(self, checkout_p # We can't use ``@pytest.mark.parametrize`` on a Django test case yaml_contents = [ - {'docs_dir': ['docs']}, - {'extra_css': 'a string here'}, - {'extra_javascript': ''}, + {"docs_dir": ["docs"]}, + {"extra_css": "a string here"}, + {"extra_javascript": ""}, ] for content in yaml_contents: yaml.safe_dump( @@ -624,12 +463,12 @@ def test_append_conf_existing_yaml_on_root_with_invalid_setting(self, checkout_p with override_settings(DOCROOT=tmpdir): self.searchbuilder.append_conf() - @patch('readthedocs.doc_builder.base.BaseBuilder.run') - @patch('readthedocs.projects.models.Project.checkout_path') + @patch("readthedocs.doc_builder.base.BaseBuilder.run") + @patch("readthedocs.projects.models.Project.checkout_path") def test_append_conf_and_none_values(self, checkout_path, run): tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, 'docs')) - yaml_file = os.path.join(tmpdir, 'mkdocs.yml') + os.mkdir(os.path.join(tmpdir, "docs")) + yaml_file = os.path.join(tmpdir, "mkdocs.yml") checkout_path.return_value = tmpdir python_env = Virtualenv( @@ -644,24 +483,24 @@ def test_append_conf_and_none_values(self, checkout_path, run): yaml.safe_dump( { - 'extra_css': None, - 'extra_javascript': None, + "extra_css": None, + "extra_javascript": None, }, - open(yaml_file, 'w'), + open(yaml_file, "w"), ) with override_settings(DOCROOT=tmpdir): builder.append_conf() config = yaml_load_safely(open(yaml_file)) self.assertEqual( - config['extra_css'], + config["extra_css"], [ "/_/static/css/badge_only.css", "/_/static/css/readthedocs-doc-embed.css", ], ) self.assertEqual( - config['extra_javascript'], + config["extra_javascript"], [ "readthedocs-data.js", "/_/static/core/js/readthedocs-doc-embed.js", @@ -823,14 +662,14 @@ def test_append_conf_existing_yaml_with_extra(self, checkout_path, run): ], ) - @patch('readthedocs.projects.models.Project.checkout_path') + @patch("readthedocs.projects.models.Project.checkout_path") def test_empty_yaml_config(self, checkout_path): tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, 'docs')) - yaml_file = os.path.join(tmpdir, 'mkdocs.yml') + os.mkdir(os.path.join(tmpdir, "docs")) + yaml_file = os.path.join(tmpdir, "mkdocs.yml") yaml.safe_dump( - '', - open(yaml_file, 'w'), + "", + open(yaml_file, "w"), ) checkout_path.return_value = tmpdir @@ -845,19 +684,19 @@ def test_empty_yaml_config(self, checkout_path): ) with self.assertRaisesMessage( - MkDocsYAMLParseError, MkDocsYAMLParseError.EMPTY_CONFIG + MkDocsYAMLParseError, MkDocsYAMLParseError.EMPTY_CONFIG ): with override_settings(DOCROOT=tmpdir): self.searchbuilder.append_conf() - @patch('readthedocs.projects.models.Project.checkout_path') + @patch("readthedocs.projects.models.Project.checkout_path") def test_yaml_config_not_returns_dict(self, checkout_path): tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, 'docs')) - yaml_file = os.path.join(tmpdir, 'mkdocs.yml') + os.mkdir(os.path.join(tmpdir, "docs")) + yaml_file = os.path.join(tmpdir, "mkdocs.yml") yaml.safe_dump( - 'test_string', - open(yaml_file, 'w'), + "test_string", + open(yaml_file, "w"), ) checkout_path.return_value = tmpdir @@ -872,7 +711,7 @@ def test_yaml_config_not_returns_dict(self, checkout_path): ) with self.assertRaisesMessage( - MkDocsYAMLParseError, MkDocsYAMLParseError.CONFIG_NOT_DICT + MkDocsYAMLParseError, MkDocsYAMLParseError.CONFIG_NOT_DICT ): with override_settings(DOCROOT=tmpdir): self.searchbuilder.append_conf()