From 75003e691dc69c7b3a00c49310d7d030cc94d948 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Wed, 7 Feb 2024 10:51:46 +0000 Subject: [PATCH] Adding sunpy sepcific tox.ini Also adding docs/conf.yml --- docs/conf.py | 375 +++++++++++++++++++++++++++++++++++++++++++++------ tox.ini | 152 ++++++++++++++------- 2 files changed, 437 insertions(+), 90 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d3c0b7405d4..bcecd9202d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,77 +1,366 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config +""" +Configuration file for the Sphinx documentation builder. +""" +# -- stdlib imports ------------------------------------------------------------ +import os +import sys +import datetime +import warnings +from packaging.version import Version -# -- Project information ----------------------------------------------------- +# -- Read the Docs Specific Configuration -------------------------------------- +# This needs to be done before sunpy is imported +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if on_rtd: + os.environ['SUNPY_CONFIGDIR'] = '/home/docs/' + os.environ['HOME'] = '/home/docs/' + os.environ['LANG'] = 'C' + os.environ['LC_ALL'] = 'C' + os.environ['PARFIVE_HIDE_PROGRESS'] = 'True' -project = "sunpy" -copyright = "2022, The SunPy Community" -author = "The SunPy Community" +# -- Check for dependencies ---------------------------------------------------- +from sunpy.util import missing_dependencies_by_extra # NOQA: E402 + +missing_requirements = missing_dependencies_by_extra("sunpy")["docs"] +if missing_requirements: + print( + f"The {' '.join(missing_requirements.keys())} package(s) could not be found and " + "is needed to build the documentation, please install the 'docs' requirements." + ) + sys.exit(1) + +from matplotlib import MatplotlibDeprecationWarning # NOQA: E402 +from ruamel.yaml import YAML # NOQA: E402 +from sphinx_gallery.sorting import ExampleTitleSortKey, ExplicitOrder # NOQA: E402 +from sunpy_sphinx_theme import PNG_ICON # NOQA: E402 + +from astropy.utils.exceptions import AstropyDeprecationWarning # NOQA: E402 + +import sunpy # NOQA: E402 +from sunpy import __version__ # NOQA: E402 +from sunpy.util.exceptions import SunpyDeprecationWarning, SunpyPendingDeprecationWarning # NOQA: E402 + +# -- Project information ------------------------------------------------------- +project = 'sunpy' +author = 'The SunPy Community' +copyright = f'{datetime.datetime.now().year}, {author}' + +# Register remote data option with doctest +import doctest # NOQA: E402 + +REMOTE_DATA = doctest.register_optionflag('REMOTE_DATA') # The full version, including alpha/beta/rc tags -from sunpy import __version__ release = __version__ +sunpy_version = Version(__version__) +is_release = not(sunpy_version.is_prerelease or sunpy_version.is_devrelease) + +# We want to make sure all the following warnings fail the build +warnings.filterwarnings("error", category=SunpyDeprecationWarning) +warnings.filterwarnings("error", category=SunpyPendingDeprecationWarning) +warnings.filterwarnings("error", category=MatplotlibDeprecationWarning) +warnings.filterwarnings("error", category=AstropyDeprecationWarning) + +# -- SunPy Sample Data and Config ---------------------------------------------- +# We set the logger to debug so that we can see any sample data download errors +# in the CI, especially RTD. +ori_level = sunpy.log.level +sunpy.log.setLevel("DEBUG") + +import sunpy.data.sample # NOQA: E402 + +sunpy.data.sample.download_all() +sunpy.log.setLevel(ori_level) -# -- General configuration --------------------------------------------------- +# For the linkcheck +linkcheck_ignore = [r"https://doi.org/\d+", + r"https://element.io/\d+", + r"https://github.com/\d+", + r"https://docs.sunpy.org/\d+"] +linkcheck_anchors = False + +# -- General configuration ----------------------------------------------------- +# sphinxext-opengraph +ogp_image = "https://raw.githubusercontent.com/sunpy/sunpy-logo/master/generated/sunpy_logo_word.png" +ogp_use_first_image = True +ogp_description_length = 160 +ogp_custom_meta_tags = [ + '', +] + +# Suppress warnings about overriding directives as we overload some of the +# doctest extensions. +suppress_warnings = ['app.add_directive', ] # Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named "sphinx.ext.*") or your custom +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.inheritance_diagram", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.doctest", - "sphinx.ext.mathjax", - "sphinx_automodapi.automodapi", - "sphinx_automodapi.smart_resolver", + 'matplotlib.sphinxext.plot_directive', + 'sphinx_automodapi.automodapi', + 'sphinx_automodapi.smart_resolver', + 'sphinx_changelog', + 'sphinx_gallery.gen_gallery', + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.doctest', + 'sphinx.ext.inheritance_diagram', + 'sphinx.ext.intersphinx', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', + 'sunpy.util.sphinx.doctest', + 'sunpy.util.sphinx.generate', + "sphinxext.opengraph", + 'sphinx_design', + 'sphinx_copybutton', + 'hoverxref.extension', ] +# Set automodapi to generate files inside the generated directory +automodapi_toctreedirnm = "generated/api" + # Add any paths that contain templates here, relative to this directory. -# templates_path = ["_templates"] +# templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +html_extra_path = ['robots.txt'] + +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +if is_release: + exclude_patterns.append('dev_guide/contents/*') # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: -source_suffix = ".rst" +source_suffix = '.rst' # The master toctree document. -master_doc = "index" +master_doc = 'index' + +# The reST default role (used for this markup: `text`) to use for all +# documents. Set to the "smart" one. +default_role = 'obj' + +# Disable having a separate return type row +napoleon_use_rtype = False + +# Disable google style docstrings +napoleon_google_docstring = False + +# Disable the use of param, which prevents a distinct "Other Parameters" section +napoleon_use_param = False -# -- Options for intersphinx extension --------------------------------------- +# Enable nitpicky mode, which forces links to be non-broken +nitpicky = True +# This is not used. See docs/nitpick-exceptions file for the actual listing. +nitpick_ignore = [] +for line in open('nitpick-exceptions'): + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore.append((dtype, target)) + +# -- Options for intersphinx extension ----------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"python": ("https://docs.python.org/", None)} +intersphinx_mapping = { + "python": ( + "https://docs.python.org/3/", + (None, "http://www.astropy.org/astropy-data/intersphinx/python3.inv"), + ), + "numpy": ( + "https://numpy.org/doc/stable/", + (None, "http://www.astropy.org/astropy-data/intersphinx/numpy.inv"), + ), + "scipy": ( + "https://docs.scipy.org/doc/scipy/reference/", + (None, "http://www.astropy.org/astropy-data/intersphinx/scipy.inv"), + ), + "aiapy": ("https://aiapy.readthedocs.io/en/stable/", None), + "asdf": ("https://asdf.readthedocs.io/en/stable/", None), + "astropy": ("https://docs.astropy.org/en/stable/", None), + "dask": ("https://docs.dask.org/en/stable/", None), + "drms": ("https://docs.sunpy.org/projects/drms/en/v0.6.4.post1/", None), + "hvpy": ("https://hvpy.readthedocs.io/en/latest/", None), + "matplotlib": ("https://matplotlib.org/stable", None), + "mpl_animators": ("https://docs.sunpy.org/projects/mpl-animators/en/stable/", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), + "parfive": ("https://parfive.readthedocs.io/en/stable/", None), + "reproject": ("https://reproject.readthedocs.io/en/stable/", None), + "skimage": ("https://scikit-image.org/docs/stable/", None), + "spiceypy": ("https://spiceypy.readthedocs.io/en/stable/", None), + "sunkit_image": ("https://docs.sunpy.org/projects/sunkit-image/en/stable/", None), + "sunkit_instruments": ("https://docs.sunpy.org/projects/sunkit-instruments/en/stable/", None), + "zeep": ("https://docs.python-zeep.org/en/stable/", None), +} + +# -- Options for hoverxref ----------------------------------------------------- +if os.environ.get("READTHEDOCS"): + hoverxref_api_host = "https://readthedocs.org" + + if os.environ.get("PROXIED_API_ENDPOINT"): + # Use the proxied API endpoint + # A RTD thing to avoid a CSRF block when docs are using a custom domain + hoverxref_api_host = "/_" -# -- Options for HTML output ------------------------------------------------- +hoverxref_auto_ref = False +hoverxref_domains = ["py"] +hoverxref_mathjax = True +hoverxref_modal_hover_delay = 500 +hoverxref_tooltip_maxwidth = 600 # RTD main window is 696px +hoverxref_intersphinx = list(intersphinx_mapping.keys()) +hoverxref_role_types = { + # Roles within the py domain + "attr": "tooltip", + "class": "tooltip", + "const": "tooltip", + "data": "tooltip", + "exc": "tooltip", + "func": "tooltip", + "meth": "tooltip", + "mod": "tooltip", + "obj": "tooltip", + # Roles within the std domain + "confval": "tooltip", + "hoverxref": "tooltip", + "ref": "tooltip", # Would be used by hoverxref_auto_ref if we set it to True + "term": "tooltip", +} +# -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "alabaster" + +html_theme = "sunpy" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] - -# By default, when rendering docstrings for classes, sphinx.ext.autodoc will -# make docs with the class-level docstring and the class-method docstrings, -# but not the __init__ docstring, which often contains the parameters to -# class constructors across the scientific Python ecosystem. The option below -# will append the __init__ docstring to the class-level docstring when rendering -# the docs. For more options, see: -# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autoclass_content -autoclass_content = "both" +# html_static_path = ['_static'] + +# Render inheritance diagrams in SVG +graphviz_output_format = "svg" + +graphviz_dot_args = [ + '-Nfontsize=10', + '-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif', + '-Efontsize=10', + '-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif', + '-Gfontsize=10', + '-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif' +] + +# -- Sphinx Gallery ------------------------------------------------------------ +# JSOC email os env +# see https://github.com/sunpy/sunpy/wiki/Home:-JSOC +os.environ["JSOC_EMAIL"] = "jsoc@sunpy.org" +sphinx_gallery_conf = { + 'backreferences_dir': os.path.join('generated', 'modules'), + 'filename_pattern': '^((?!skip_).)*$', + 'examples_dirs': os.path.join('..', 'examples'), + 'subsection_order': ExplicitOrder([ + '../examples/acquiring_data', + '../examples/map', + '../examples/map_transformations', + '../examples/time_series', + '../examples/units_and_coordinates', + '../examples/plotting', + '../examples/differential_rotation', + '../examples/saving_and_loading_data', + '../examples/computer_vision_techniques', + '../examples/showcase', + ]), + 'within_subsection_order': ExampleTitleSortKey, + 'gallery_dirs': os.path.join('generated', 'gallery'), + 'matplotlib_animations': True, + # Comes from the theme. + "default_thumb_file": PNG_ICON, + 'abort_on_example_error': False, + 'plot_gallery': 'True', + 'remove_config_comments': True, + 'doc_module': ('sunpy'), + 'only_warn_on_example_error': True, +} + +# -- Linking to OpenCV docs by using rst_epilog -------------------------------- +try: + import requests + from bs4 import BeautifulSoup + + base_url = "https://docs.opencv.org" + + # The stable-version docs are the first item in the second list on the main page + all_docs = BeautifulSoup(requests.get(base_url).text, 'html.parser') + version = all_docs.find_all('ul')[1].li.a.attrs['href'][2:] # strip leading "./" + + # Find the relative URL to the page for the `cv` namespace + stable_docs = BeautifulSoup(requests.get(f"{base_url}/{version}/namespaces.html").text, + 'html.parser') + cv_namespace = stable_docs.find("a", string="cv").attrs['href'] + + # Find the relative URL for warpAffine/filter2D in the `cv` namespace + all_cv = BeautifulSoup(requests.get(f"{base_url}/{version}/{cv_namespace}").text, 'html.parser') + warpAffine = all_cv.find("a", string="warpAffine").attrs['href'][6:] # strip leading "../../" + filter2D = all_cv.find("a", string="filter2D").attrs['href'][6:] # strip leading "../../" + + # Construct the full URL for warpAffine/filter2D + warpAffine_full = f"{base_url}/{version}/{warpAffine}" + filter2D_full = f"{base_url}/{version}/{filter2D}" +except Exception: + # In the event of any failure (e.g., no network connectivity) + warpAffine_full = "" + filter2D_full = "" + +rst_epilog = f""" +.. |cv2_warpAffine| replace:: **cv2.warpAffine()** +.. _cv2_warpAffine: {warpAffine_full} +.. |cv2_filter2D| replace:: **cv2.filter2D()** +.. _cv2_filter2D: {filter2D_full} +""" + +# -- Options for sphinx-copybutton --------------------------------------------- +# Python Repl + continuation, Bash, ipython and qtconsole + continuation, jupyter-console + continuation +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True + +# -- Stability Page ------------------------------------------------------------ +with open('./reference/sunpy_stability.yaml') as estability: + yaml = YAML(typ='rt') + sunpy_modules = yaml.load(estability.read()) + +html_context = { + 'sunpy_modules': sunpy_modules, + 'is_development': not is_release, +} + + +def rstjinja(app, docname, source): + """ + Render our pages as a jinja template for fancy templating goodness. + """ + # Make sure we're outputting HTML + if app.builder.format != 'html': + return + files_to_render = ["reference/stability", "dev_guide/index"] + if docname in files_to_render: + print(f"Jinja rendering {docname}") + rendered = app.builder.templates.render_string( + source[0], app.config.html_context + ) + source[0] = rendered + + +# -- Sphinx setup -------------------------------------------------------------- +def setup(app): + # Generate the stability page + app.connect("source-read", rstjinja) diff --git a/tox.ini b/tox.ini index 951eb79fa65..f25061c7564 100644 --- a/tox.ini +++ b/tox.ini @@ -1,63 +1,121 @@ [tox] min_version = 4.0 envlist = - py{38,39,310,311}-test - py38-test-oldestdeps - build_docs -isolated_build = true + py{310,311,312}{,-oldestdeps,-devdeps,-online,-figure,-conda} + build_docs{,-gallery} + codestyle + base_deps [testenv] -# tox environments are constructed with so-called 'factors' (or terms) -# separated by hyphens, e.g. test-devdeps-cov. Lines below starting with factor: -# will only take effect if that factor is included in the environment name. To -# see a list of example environments that can be run, along with a description, -# run: -# -# tox -l -v -# +pypi_filter = file://.test_package_pins.txt +allowlist_externals= + /bin/bash + /usr/bin/bash +# Run the tests in a temporary directory to make sure that we don't import sunpy from the source tree +changedir = .tmp/{envname} description = run tests + conda: with the latest conda version of key dependencies + devdeps: with the latest developer version of key dependencies + figure: runs the figure test suite. oldestdeps: with the oldest supported version of key dependencies - -# Pass through the following environment variables which may be needed for the CI -pass_env = - HOME - WINDIR - LC_ALL - LC_CTYPE - CC - CI - TRAVIS - -# Suppress display of matplotlib plots generated during docs build -set_env = - MPLBACKEND=agg - -# Run the tests in a temporary directory to make sure that we don't import -# the package from the source tree -change_dir = .tmp/{envname} - + online: that require remote data (as well as the offline ones) +setenv = + MPLBACKEND = agg + SUNPY_SAMPLEDIR = {env:SUNPY_SAMPLEDIR:{toxinidir}/.tox/sample_data/} + PYTEST_COMMAND = pytest -vvv -r fEs --pyargs sunpy --cov-report=xml --cov=sunpy --cov-config={toxinidir}/.coveragerc {toxinidir}/docs + devdeps,build_docs,online: HOME = {envtmpdir} + PARFIVE_HIDE_PROGRESS = True + NO_VERIFY_HELIO_SSL = 1 + devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple deps = - oldestdeps: minimum_dependencies - pytest-cov - -# The following indicates which extras_require from setup.cfg will be installed + # devdeps is intended to be used to install the latest developer version of key dependencies. + devdeps: astropy>=0.0.dev0 + devdeps: git+https://github.com/asdf-format/asdf + devdeps: git+https://github.com/astropy/asdf-astropy + devdeps: git+https://github.com/astropy/reproject + devdeps: git+https://github.com/Cadair/parfive + devdeps: git+https://github.com/MAVENSDC/cdflib + devdeps: git+https://github.com/sunpy/mpl-animators + devdeps: matplotlib>=0.0.dev0 + devdeps: numpy>=0.0.dev0 + devdeps: pandas>=0.0.dev0 + devdeps: pluggy>=0.0.dev0 + devdeps: pytest>=0.0.dev0 + # oldestdeps we pin against to ensure correct versions + oldestdeps: asdf-astropy<0.3.0 + oldestdeps: asdf<2.12.0 + oldestdeps: astropy<5.3.0 + oldestdeps: beautifulsoup4<4.12.0 + oldestdeps: cdflib<0.4.5 + oldestdeps: dask[array]<2022.6.0 + oldestdeps: drms<0.8 + oldestdeps: glymur<0.9.2 + oldestdeps: h5netcdf<1.1.0 + oldestdeps: matplotlib<3.6.0 + oldestdeps: numpy<1.22.0 + oldestdeps: opencv-python<4.7.0.68 + oldestdeps: pandas<1.5.0 + oldestdeps: parfive<2.1.0 + oldestdeps: pytest-xdist<3.1 + oldestdeps: pytest<7.2 + oldestdeps: python-dateutil<2.9.0 + oldestdeps: scikit-image<0.20.0; platform_system!='Darwin' or platform_machine!='arm64' + # scikit-image 0.20 is the first with wheels for macOS arm64 + oldestdeps: scikit-image<0.21.0; platform_system=='Darwin' and platform_machine=='arm64' + oldestdeps: scipy<1.9.0 + oldestdeps: tqdm<4.65.0 + oldestdeps: zeep<4.2.0 + # Figure tests need a tightly controlled environment + figure-!devdeps: astropy==6.0.0 + figure-!devdeps: matplotlib==3.8.2 + figure-!devdeps: mpl-animators==1.1.1 + # Due to https://github.com/matplotlib/pytest-mpl/issues/216 + # Needs a release of pytest-mpl with the fix + figure: pluggy<1.4 extras = - test + all + tests +commands = + pip freeze --all --no-input + oldestdeps: python -c "import astropy.time; astropy.time.update_leap_seconds()" + !online-!figure: {env:PYTEST_COMMAND} {posargs} + online: {env:PYTEST_COMMAND} --hypothesis-show-statistics --remote-data=any {posargs} + figure: /bin/bash -c "mkdir -p ./figure_test_images; python -c 'import matplotlib as mpl; print(mpl.ft2font.__file__, mpl.ft2font.__freetype_version__, mpl.ft2font.__freetype_build_type__)' > ./figure_test_images/figure_version_info.txt" + figure: /bin/bash -c "pip freeze >> ./figure_test_images/figure_version_info.txt" + figure: /bin/bash -c "cat ./figure_test_images/figure_version_info.txt" + figure: python -c "import sunpy.tests.helpers as h; print(h.get_hash_library_name())" + figure: {env:PYTEST_COMMAND} -m "mpl_image_compare" --mpl --remote-data=any --mpl-generate-summary=html --mpl-baseline-path=https://raw.githubusercontent.com/sunpy/sunpy-figure-tests/sunpy-master/figures/{envname}/ {posargs} -commands_pre = - oldestdeps: minimum_dependencies packagename --filename requirements-min.txt - oldestdeps: pip install -r requirements-min.txt - pip freeze +[testenv:build_docs{,-gallery}] +changedir = docs +description = Invoke sphinx-build to build the HTML docs +extras = + all + docs + gallery: docs-gallery +commands = + pip freeze --all --no-input + gallery: sphinx-build -j auto --color -W --keep-going -b html -d _build/.doctrees . _build/html {posargs} + !gallery: sphinx-build -j auto --color -W --keep-going -b html -d _build/.doctrees -D plot_gallery=0 . _build/html {posargs} + python -c 'import pathlib; print("Documentation available under file://\{0\}".format(pathlib.Path(r"{toxinidir}") / "docs" / "_build" / "index.html"))' +[testenv:codestyle] +pypi_filter = +skip_install = true +description = Run all style and file checks with pre-commit +deps = + pre-commit commands = - pytest --pyargs packagename --cov sunpy --cov-report xml:coverage.xml --cov-report term-missing {posargs} + pre-commit install-hooks + pre-commit run --color always --all-files --show-diff-on-failure -[testenv:build_docs] -description = invoke sphinx-build to build the HTML docs -change_dir = - docs +[testenv:base_deps] +description = Check the test suite does not fail if all optional dependencies are missing extras = - docs +deps = + astropy + numpy + parfive[ftp] commands = - sphinx-build -j auto --color -W --keep-going -b html -d _build/.doctrees . _build/html {posargs} + python -c "import sunpy; sunpy.self_test()"