diff --git a/.flake8 b/.flake8 index 0d9abf7dd..453bbd46a 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] -max-line-length = 88 -ignore = - E203, # space before : (needed for how black formats slicing) - W503, # line break before binary operator +# E203: space before ":" | needed for how black formats slicing +# E501: line too long | Black take care of it +# W503: line break before binary operator | Black take care of it +# W605: invalid escape sequence | we escape specific characters for sphinx +ignore = E203, E501, W503, W605 exclude = setup.py,docs/conf.py,node_modules,docs,build,dist diff --git a/docs/user_guide/version-dropdown.rst b/docs/user_guide/version-dropdown.rst index 0fb064cf4..eb7227e5b 100644 --- a/docs/user_guide/version-dropdown.rst +++ b/docs/user_guide/version-dropdown.rst @@ -115,6 +115,14 @@ a few different ways: } } +By default the theme is testing the :code:`.json` file provided and outputs warnings in the Sphinx build. If this test breaks the pipeline of your docs, the test can be disabled by configuring the :code:`check_switcher` parameter in :code:`conf.py`: + +.. code-block:: python + + html_theme_options = { + # ... + "check_switcher": False + } Configure ``switcher['version_match']`` --------------------------------------- diff --git a/src/pydata_sphinx_theme/__init__.py b/src/pydata_sphinx_theme/__init__.py index 8f8043982..e7984859f 100644 --- a/src/pydata_sphinx_theme/__init__.py +++ b/src/pydata_sphinx_theme/__init__.py @@ -21,6 +21,7 @@ from pygments.formatters import HtmlFormatter from pygments.styles import get_all_styles import requests +from requests.exceptions import ConnectionError, HTTPError, RetryError from .bootstrap_html_translator import BootstrapHTML5Translator @@ -82,8 +83,10 @@ def update_config(app, env): " Set version URLs in JSON directly." ) - # check the validity of the theme swithcer file - if isinstance(theme_options.get("switcher"), dict): + # check the validity of the theme switcher file + is_dict = isinstance(theme_options.get("switcher"), dict) + should_test = theme_options.get("check_switcher", True) + if is_dict and should_test: theme_switcher = theme_options.get("switcher") # raise an error if one of these compulsory keys is missing @@ -92,22 +95,37 @@ def update_config(app, env): # try to read the json file. If it's a url we use request, # else we simply read the local file from the source directory - # it will raise an error if the file does not exist + # display a log warning if the file cannot be reached + reading_error = None if urlparse(json_url).scheme in ["http", "https"]: - content = requests.get(json_url).text + try: + request = requests.get(json_url) + request.raise_for_status() + content = request.text + except (ConnectionError, HTTPError, RetryError) as e: + reading_error = repr(e) else: - content = Path(env.srcdir, json_url).read_text() - - # check that the json file is not illformed - # it will throw an error if there is a an issue - switcher_content = json.loads(content) - missing_url = any(["url" not in e for e in switcher_content]) - missing_version = any(["version" not in e for e in switcher_content]) - if missing_url or missing_version: - raise AttributeError( - f'The version switcher "{json_url}" file is malformed' - ' at least one of the items is missing the "url" or "version" key' + try: + content = Path(env.srcdir, json_url).read_text() + except FileNotFoundError as e: + reading_error = repr(e) + + if reading_error is not None: + logger.warning( + f'The version switcher "{json_url}" file cannot be read due to the following error:\n' + f"{reading_error}" ) + else: + # check that the json file is not illformed, + # throw a warning if the file is ill formed and an error if it's not json + switcher_content = json.loads(content) + missing_url = any(["url" not in e for e in switcher_content]) + missing_version = any(["version" not in e for e in switcher_content]) + if missing_url or missing_version: + logger.warning( + f'The version switcher "{json_url}" file is malformed' + ' at least one of the items is missing the "url" or "version" key' + ) # Add an analytics ID to the site if provided analytics = theme_options.get("analytics", {}) diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf index 22a4ae582..37218f216 100644 --- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf +++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf @@ -36,6 +36,7 @@ primary_sidebar_end = sidebar-ethical-ads.html footer_items = copyright.html, sphinx-version.html secondary_sidebar_items = page-toc.html, searchbox.html, edit-this-page.html, sourcelink.html switcher = +check_switcher = True pygment_light_style = tango pygment_dark_style = monokai logo = diff --git a/tests/sites/base/missing_url.json b/tests/sites/base/missing_url.json new file mode 100644 index 000000000..b7e92ecca --- /dev/null +++ b/tests/sites/base/missing_url.json @@ -0,0 +1,14 @@ +[ + { + "name": "v0.7.1 (stable)", + "version": "0.7.1", + "url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.1/" + }, + { + "version": "0.7.0", + "url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.0/" + }, + { + "version": "0.6.3" + } +] diff --git a/tests/test_build.py b/tests/test_build.py index 9d1795d73..4c5930a96 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,4 +1,5 @@ import os +import re from pathlib import Path from shutil import copytree @@ -11,6 +12,12 @@ path_tests = Path(__file__).parent +def escape_ansi(string): + """helper function to remove ansi coloring from sphinx warnings""" + ansi_escape = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]") + return ansi_escape.sub("", string) + + class SphinxBuild: def __init__(self, app: SphinxTestApp, src: Path): self.app = app @@ -628,7 +635,12 @@ def test_show_nav_level(sphinx_build_factory): assert "checked" in checkbox.attrs -def test_version_switcher(sphinx_build_factory, file_regression): +switcher_files = ["switcher.json", "http://a.b/switcher.json", "missing_url.json"] +"the switcher files tested in test_version_switcher, not all of them exist" + + +@pytest.mark.parametrize("url", switcher_files) +def test_version_switcher(sphinx_build_factory, file_regression, url): """Regression test the version switcher dropdown HTML. Note that a lot of the switcher HTML gets populated by JavaScript, @@ -641,20 +653,28 @@ def test_version_switcher(sphinx_build_factory, file_regression): "html_theme_options": { "navbar_end": ["version-switcher"], "switcher": { - "json_url": "switcher.json", + "json_url": url, "version_match": "0.7.1", }, } } - sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides).build() - switcher = sphinx_build.html_tree("index.html").select( - ".version-switcher__container" - )[ - 0 - ] # noqa - file_regression.check( - switcher.prettify(), basename="navbar_switcher", extension=".html" - ) + factory = sphinx_build_factory("base", confoverrides=confoverrides) + sphinx_build = factory.build(no_warning=False) + + if url == "switcher.json": # this should work + index = sphinx_build.html_tree("index.html") + switcher = index.select(".version-switcher__container")[0] + file_regression.check( + switcher.prettify(), basename="navbar_switcher", extension=".html" + ) + + elif url == "http://a.b/switcher.json": # this file doesn't exist" + not_read = 'WARNING: The version switcher "http://a.b/switcher.json" file cannot be read due to the following error:\n' # noqa + assert not_read in escape_ansi(sphinx_build.warnings).strip() + + elif url == "missing_url.json": # this file is missing the url key for one version + missing_url = 'WARNING: The version switcher "missing_url.json" file is malformed at least one of the items is missing the "url" or "version" key' # noqa + assert escape_ansi(sphinx_build.warnings).strip() == missing_url def test_theme_switcher(sphinx_build_factory, file_regression):