diff --git a/.github/workflows/sphinx_asdf_ci.yml b/.github/workflows/sphinx_asdf_ci.yml index 1ccbf1e..08449a2 100644 --- a/.github/workflows/sphinx_asdf_ci.yml +++ b/.github/workflows/sphinx_asdf_ci.yml @@ -9,6 +9,14 @@ on: pull_request: branches: +# Only cancel in-progress jobs or runs for the current workflow +# This cancels the already triggered workflows for a specific PR without canceling +# other instances of this workflow (other PRs, scheduled triggers, etc) when something +# within that PR re-triggers this CI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: tox_pytest: name: ${{ matrix.name }} @@ -16,6 +24,11 @@ jobs: strategy: matrix: include: + - name: Python 3.12 Tests + python-version: "3.12-dev" + os: ubuntu-latest + toxenv: py312 + - name: Python 3.11 Tests python-version: "3.11" os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3aa5fff..7525b8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,36 +4,58 @@ repos: rev: v4.4.0 hooks: - id: check-added-large-files + - id: check-ast - id: check-case-conflict - id: check-yaml args: ["--unsafe"] + - id: check-toml + - id: check-merge-conflict + - id: check-symlinks - id: debug-statements + - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 hooks: - - id: pyupgrade - args: ["--py38-plus"] + - id: python-check-blanket-noqa + - id: python-check-mock-methods + - id: rst-directive-colons + - id: rst-inline-touching-normal + - id: text-unicode-replacement-char -- repo: https://github.com/pycqa/isort - rev: 5.12.0 +- repo: https://github.com/codespell-project/codespell + rev: v2.2.5 hooks: - - id: isort + - id: codespell + args: ["--write-changes"] + additional_dependencies: + - tomli + +- repo: https://github.com/ikamensh/flynt/ + rev: '1.0.1' + hooks: + - id: flynt + exclude: "asdf/(extern||_jsonschema)/.*" + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.0.288' + hooks: + - id: ruff + args: ["--fix"] - repo: https://github.com/psf/black rev: 23.9.1 hooks: - id: black -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 +- repo: https://github.com/asottile/blacken-docs + rev: '1.16.0' hooks: - - id: flake8 + - id: blacken-docs -- repo: https://github.com/PyCQA/bandit - rev: 1.7.5 +- repo: https://github.com/abravalheri/validate-pyproject + rev: "v0.14" hooks: - - id: bandit - args: ["-c", "bandit.yaml"] + - id: validate-pyproject diff --git a/README.rst b/README.rst index 71a981c..2ba2e1e 100644 --- a/README.rst +++ b/README.rst @@ -171,8 +171,10 @@ resolved as links, include the following in your ``docs/conf.py`` file: .. code-block:: python asdf_schema_reference_mappings = [ - ('tag:stsci.edu:asdf', - 'http://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/'), + ( + "tag:stsci.edu:asdf", + "http://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/", + ), ] Inline documentation diff --git a/bandit.yaml b/bandit.yaml deleted file mode 100644 index f0bcd75..0000000 --- a/bandit.yaml +++ /dev/null @@ -1,8 +0,0 @@ -skips: [ B101, B102, B301, B701, B403 ] - -exclude_dirs: - - .eggs - - .git - - .pytest_cache - - .tox - - __pycache__ diff --git a/pyproject.toml b/pyproject.toml index bc1b550..faf1b76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,48 @@ +[project] +name = "sphinx-asdf" +description = "Sphinx plugin for generating documentation from ASDF schemas" +readme = 'README.rst' +license = { file = 'LICENSE' } +authors = [{ name = 'The ASDF Developers', email = 'help@stsci.edu' }] +requires-python = '>=3.9' +classifiers = [ + 'Development Status :: 5 - Production/Stable', + "License :: OSI Approved :: BSD License", + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', +] +dynamic = [ + 'version', +] +dependencies = [ + "asdf", + "astropy>=5.0.4", + "docutils", + "mistune>=3", + "packaging", + "sphinx", + "sphinx-astropy", + "sphinx_bootstrap_theme", + "sphinx-rtd-theme", + "toml", + +] +[project.optional-dependencies] +tests = [ + "pytest", +] +[project.urls] +'documentation' = 'https://sphinx-asdf.readthedocs.io/en/stable' +'repository' = 'https://github.com/asdf-format/sphinx-asdf' +'tracker' = 'https://github.com/asdf-format/sphinx-asdf/issues' + +[tool.setuptools.packages.find] +include = ['sphinx_asdf*'] + [build-system] requires = ["setuptools>=42", "setuptools_scm[toml]>=3.4", "wheel"] build-backend = "setuptools.build_meta" @@ -20,5 +65,26 @@ force-exclude = ''' [tool.isort] profile = "black" -filter_files = true -line_length = 120 +filter-files = true +line-length = 120 + +[tool.ruff] +target-version = "py38" +line-length = 120 +select = [ + # minimal set to match pre-ruff behavior + "E", # pycodestyle + "F", # pyflakes, autoflake + "I", # isort + "S", # bandit + "UP", # pyupgrade + "RUF", # ruff specific, includes yesqa +] +extend-ignore = [ + "F403", "F405", # uses if import * should be removed + "S101", # asserts used in tests + "RUF012", # we don't use typing +] + +[tool.codespell] +skip="*.pdf,*.asdf,.tox,build,./tags,.git,docs/_build" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index da5858f..0000000 --- a/setup.cfg +++ /dev/null @@ -1,51 +0,0 @@ -[metadata] -name = sphinx-asdf -package_name = sphinx-asdf -provides = sphinx_asdf -author = The ASDF Developers -license = BSD-3-Clause -license_file = LICENSE -description = Sphinx plugin for generating documentation from ASDF schemas -long_description = file: README.rst -url = https://github.com/spacetelescope/sphinx-asdf -github_project = spacetelescope/sphinx-asdf - -[options] -packages = find: -python_requires = >=3.9 -include_package_data = True -setup_requires = setuptools_scm -install_requires = - asdf - astropy>=5.0.4 - docutils - mistune>=3 - packaging - sphinx - sphinx-astropy - sphinx_bootstrap_theme - sphinx-rtd-theme - toml - -[options.extras_require] -tests = - pytest - -[flake8] -# E101 - mix of tabs and spaces -# W191 - use of tabs -# W291 - trailing whitespace -# W292 - no newline at end of file -# W293 - trailing whitespace -# W391 - blank line at end of file -# E111 - 4 spaces per indentation level -# E112 - 4 spaces per indentation level -# E113 - 4 spaces per indentation level -# E901 - SyntaxError or IndentationError -# E902 - IOError -select = E101,W191,W291,W292,W293,W391,E111,E112,E113,E901,E902 -exclude = static,templates - -[tool:pytest] -filterwarnings = - ignore::DeprecationWarning diff --git a/sphinx_asdf/__init__.py b/sphinx_asdf/__init__.py index 63047f9..ce7553d 100644 --- a/sphinx_asdf/__init__.py +++ b/sphinx_asdf/__init__.py @@ -1,7 +1,5 @@ import os -from sphinx.builders.html import StandaloneHTMLBuilder - from .asdf2rst import AsdfDirective, RunCodeDirective from .connections import ( add_labels_to_nodes, @@ -10,7 +8,7 @@ on_build_finished, update_app_config, ) -from .directives import AsdfAutoschemas, AsdfSchema, schema_def +from .directives import AsdfAutoschemas, AsdfSchema from .nodes import add_asdf_nodes diff --git a/sphinx_asdf/asdf2rst.py b/sphinx_asdf/asdf2rst.py index 6513493..1853fb9 100644 --- a/sphinx_asdf/asdf2rst.py +++ b/sphinx_asdf/asdf2rst.py @@ -29,7 +29,7 @@ def run(self): try: try: - exec(code, GLOBALS) + exec(code, GLOBALS) # noqa: S102 except Exception: print(code) raise @@ -75,7 +75,7 @@ def _block_to_string(block): if header["flags"] & key: human_flags.append(val) if len(human_flags): - lines.append(" flags: {}".format(" | ".join(human_flags))) + lines.append(f" flags: {' | '.join(human_flags)}") if header["compression"] and header["compression"] != b"\0\0\0\0": lines.append(f" compression: {header['compression']}") lines.append(f" allocated_size: {header['allocated_size']}") @@ -100,8 +100,8 @@ def run(self): cwd = os.getcwd() os.chdir(TMPDIR) - show_header = not ("no_header" in self.arguments) - show_bocks = not ("no_blocks" in self.arguments) + show_header = "no_header" not in self.arguments + show_bocks = "no_blocks" not in self.arguments parts = [] try: diff --git a/sphinx_asdf/conf.py b/sphinx_asdf/conf.py index d7ff972..1dc1d27 100644 --- a/sphinx_asdf/conf.py +++ b/sphinx_asdf/conf.py @@ -1,16 +1,8 @@ -import datetime +# datetime is kept here as some downstream packages currently +# expect it to be imported when this file is star imported +import datetime # noqa: F401 import os import sys -from pathlib import Path - -import numpy -import toml - -# Ensure documentation examples are determinstically random. -try: - numpy.random.seed(int(os.environ["SOURCE_DATE_EPOCH"])) -except KeyError: - pass try: from sphinx_astropy.conf.v1 import * diff --git a/sphinx_asdf/connections.py b/sphinx_asdf/connections.py index d5ab412..36d8bf8 100644 --- a/sphinx_asdf/connections.py +++ b/sphinx_asdf/connections.py @@ -1,7 +1,6 @@ import logging import os import posixpath -import warnings import docutils import packaging.version diff --git a/sphinx_asdf/directives.py b/sphinx_asdf/directives.py index 93c4179..2e8e8d0 100644 --- a/sphinx_asdf/directives.py +++ b/sphinx_asdf/directives.py @@ -204,11 +204,17 @@ def _create_reference(self, refname, shorten=False): schema_id = self._resolve_reference(schema_id) if fragment: components = fragment.split("/") - fragment = "#{}".format("-".join(components[1:])) + fragment = f"#{'-'.join(components[1:])}" if shorten and not schema_id: refname = components[-1] elif shorten: - rename = schema_id + # TODO this should probably be: + # refname = schema_id + # as it was previously + # rename = schema_id + # and ruff cleaned this up as unused. However, changing it + # to refname breaks some downstream packages + pass return refname, schema_id + fragment @@ -253,10 +259,10 @@ def _process_validation_keywords(self, schema, typename=None, path=""): if not ("minLength" in schema or "maxLength" in schema): node_list.append(nodes.emphasis(text="No length restriction")) if schema.get("minLength", 0): - text = "Minimum length: {}".format(schema["minLength"]) + text = f"Minimum length: {schema['minLength']}" node_list.append(nodes.line(text=text)) if "maxLength" in schema: - text = "Maximum length: {}".format(schema["maxLength"]) + text = f"Maximum length: {schema['maxLength']}" node_list.append(nodes.line(text=text)) if "pattern" in schema: node_list.append(nodes.line(text="Must match the following pattern:")) @@ -264,13 +270,13 @@ def _process_validation_keywords(self, schema, typename=None, path=""): elif typename == "array": if schema.get("minItems", 0): - text = "Minimum length: {}".format(schema["minItems"]) + text = f"Minimum length: {schema['minItems']}" node_list.append(nodes.line(text=text)) if "maxItems" in schema: - text = "Maximum length: {}".format(schema["maxItems"]) + text = f"Maximum length: {schema['maxItems']}" node_list.append(nodes.line(text=text)) if "additionalItems" in schema and "items" in schema: - if isinstance(schema["items"], list) and schema["additionalItems"] == False: + if isinstance(schema["items"], list) and schema["additionalItems"] is False: node_list.append(nodes.emphasis(text="Additional items not permitted")) elif not ("minItems" in schema or "maxItems" in schema): node_list.append(nodes.emphasis(text="No length restriction")) @@ -281,10 +287,10 @@ def _process_validation_keywords(self, schema, typename=None, path=""): # TODO: more numerical validation keywords elif typename in ["integer", "number"]: if "minimum" in schema: - text = "Minimum value: {}".format(schema["minimum"]) + text = f"Minimum value: {schema['minimum']}" node_list.append(nodes.line(text=text)) if "maximum" in schema: - text = "Maximum value: {}".format(schema["maximum"]) + text = f"Maximum value: {schema['maximum']}" node_list.append(nodes.line(text=text)) if "enum" in schema: diff --git a/sphinx_asdf/md2rst.py b/sphinx_asdf/md2rst.py index c8f35b2..fb511c6 100644 --- a/sphinx_asdf/md2rst.py +++ b/sphinx_asdf/md2rst.py @@ -4,7 +4,6 @@ rst converter that used to be in this file. """ -import re import textwrap import mistune diff --git a/sphinx_asdf/nodes.py b/sphinx_asdf/nodes.py index d92dbc5..5c3ea47 100644 --- a/sphinx_asdf/nodes.py +++ b/sphinx_asdf/nodes.py @@ -1,12 +1,8 @@ from docutils import nodes -from jinja2 import Environment -template_env = Environment() -headerlink_template = template_env.from_string( +headerlink_template = """ + ¶ """ - ¶ - """ -) class schema_doc(nodes.compound): @@ -57,7 +53,7 @@ def visit_html(self, node): self.body.append(r'
") if node.get("ref", None) is not None: - self.body.append("{}".format(node.get("ref"), node.get("typ"))) + self.body.append(f"{node.get('typ')}") else: self.body.append(node.get("typ")) self.body.append(" | ") @@ -111,7 +107,7 @@ def depart_html(self, node): class asdf_ref(nodes.line): def visit_html(self, node): - self.body.append(''.format(node.get("href"))) + self.body.append(f"") def depart_html(self, node): self.body.append(r"") diff --git a/tests/test_sphinx_asdf.py b/tests/test_sphinx_asdf.py index 4780540..550a8b6 100644 --- a/tests/test_sphinx_asdf.py +++ b/tests/test_sphinx_asdf.py @@ -90,9 +90,9 @@ def test_basic_build(app, status, warning): # Test each of the generated schema documents for name in ["foo", "bar", "core/baz"]: doctree_path = app.doctreedir / "generated" / f"{name}.doctree" - doc = pickle.loads(doctree_path.read_bytes()) + doc = pickle.loads(doctree_path.read_bytes()) # noqa: S301 - title = list(doc.findall(nodes.title))[0] + title = next(iter(doc.findall(nodes.title))) assert title.astext() == name schema_top = list(doc.findall(sa_nodes.schema_doc))