diff --git a/.flake8 b/.flake8 deleted file mode 100644 index c8d89a3..0000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -ban-relative-imports = true -inline-quotes = " -ignore = - # Barring function calls in default args. Ha, no. - B008, - # See https://github.com/PyCQA/flake8-bugbear/issues/131 - B306, - # (flake8 default) old PEP8 boolean operator line breaks - W503, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 114da69..935e20f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,47 +2,37 @@ name: CI on: push: + branches-ignore: + - "wip*" + tags: + - "v*" pull_request: - release: - types: [published] schedule: # Daily at 3:11 - cron: "11 3 * * *" - -env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_PYTHON_VERSION_WARNING: "1" + workflow_dispatch: jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - uses: pre-commit/action@v3.0.0 - list: runs-on: ubuntu-latest outputs: noxenvs: ${{ steps.noxenvs-matrix.outputs.noxenvs }} steps: - - uses: actions/checkout@v3 - - name: Set up nox - uses: wntrblm/nox@2023.04.22 + - uses: actions/checkout@v6 + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true - id: noxenvs-matrix run: | echo >>$GITHUB_OUTPUT noxenvs=$( - nox --list-sessions | - grep '^* ' | - cut -d ' ' -f 2- | - jq --raw-input --slurp 'split("\n") | map(select(. != ""))' + uvx nox --list-sessions --json | jq '[.[].session]' ) ci: needs: list runs-on: ${{ matrix.os }} + strategy: fail-fast: false matrix: @@ -50,7 +40,7 @@ jobs: noxenv: ${{ fromJson(needs.list.outputs.noxenvs) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Install dependencies run: > sudo apt-get update && @@ -60,13 +50,21 @@ jobs: run: brew install enchant if: runner.os == 'macOS' && startsWith(matrix.noxenv, 'docs') - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: - python-version: "3.x" - - name: Set up nox - uses: wntrblm/nox@2023.04.22 + python-version: | + 3.11 + 3.12 + 3.13 + allow-prereleases: true + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + - name: Run nox - run: nox -s "${{ matrix.noxenv }}" + run: uvx nox -s "${{ matrix.noxenv }}" packaging: needs: ci @@ -74,26 +72,27 @@ jobs: environment: name: PyPI url: https://pypi.org/p/sphinx-json-schema-spec + permissions: contents: write id-token: write steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 + - uses: actions/checkout@v6 + - name: Set up uv + uses: astral-sh/setup-uv@v7 with: - python-version: "3.x" - - name: Install dependencies - run: python -m pip install build - - name: Create packages - run: python -m build . + enable-cache: true + + - name: Build our distributions + run: uv run --with 'build[uv]' -m build --installer=uv + - name: Publish to PyPI if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 - name: Create a Release if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | dist/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3bbffba..ad17df0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: check-ast - id: check-docstring-first @@ -13,11 +13,18 @@ repos: - id: mixed-line-ending args: [--fix, lf] - id: trailing-whitespace - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.14.8" hooks: - - id: isort + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 25.12.0 + hooks: + - name: black + id: black + args: ["--line-length", "79"] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.0-alpha.9-for-vscode" + rev: "v4.0.0-alpha.8" hooks: - id: prettier diff --git a/.readthedocs.yml b/.readthedocs.yml index f78bde4..e6ab173 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,10 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.11" + sphinx: builder: dirhtml configuration: docs/conf.py @@ -8,8 +13,5 @@ sphinx: formats: all python: - version: "3.8" install: - requirements: docs/requirements.txt - - method: pip - path: . diff --git a/docs/conf.py b/docs/conf.py index befa80a..f3524e8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,39 +1,16 @@ -# -*- 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 - import importlib.metadata import re -# -- Project information ----------------------------------------------------- - project = "sphinx-json-schema-spec" author = "Julian Berman" copyright = "2013, " + author -# version: The short X.Y version -# release: The full version, including alpha/beta/rc tags. release = importlib.metadata.version("sphinx_json_schema_spec") version = release.partition("-")[0] - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = "1.0" - +language = "en" default_role = "any" -primary_domain = "rst" - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named "sphinx.ext.*") or your custom -# ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", @@ -44,191 +21,44 @@ "sphinx.ext.todo", "sphinx_json_schema_spec", "sphinxcontrib.spelling", + "sphinxext.opengraph", ] -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = [".rst", ".md"] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# 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 = [u"_build", "Thumbs.db", ".DS_Store"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - - -# -- Options for HTML output ------------------------------------------------- +pygments_style = "lovelace" +pygments_dark_style = "one-dark" -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = "furo" -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# 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"] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``["localtoc.html", "relations.html", "sourcelink.html", -# "searchbox.html"]``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "sphinxjsonschemaspecdoc" - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ("letterpaper" or "a4paper"). - # - # "papersize": "letterpaper", - - # The font size ("10pt", "11pt" or "12pt"). - # - # "pointsize": "10pt", - - # Additional stuff for the LaTeX preamble. - # - # "preamble": "", - - # Latex figure (float) alignment - # - # "figure_align": "htbp", -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - master_doc, - "Sphinx JSON Schema Spec.tex", - project, - author, - "manual", - ), -] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ( - master_doc, - project, - project, - [author], - 1, - ), -] +def entire_domain(host): + return r"http.?://" + re.escape(host) + r"($|/.*)" -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - project, - project, - author, - project, - "", - "Miscellaneous", - ), +linkcheck_ignore = [ + entire_domain("img.shields.io"), + "https://github.com/python-jsonschema/sphinx-json-schema-spec/actions", + "https://github.com/python-jsonschema/sphinx-json-schema-spec/workflows/CI/badge.svg", ] +# = Extensions = -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = "" - -# A unique identification for the text. -# -# epub_uid = "" - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for autodoc extension ------------------------------------------- - -autodoc_default_options = { - "members": True, - "member-order": "bysource", -} - -# -- Options for autosectionlabel extension ---------------------------------- +# -- autosectionlabel -- autosectionlabel_prefix_document = True -# -- Options for intersphinx extension --------------------------------------- +# -- intersphinx -- -# Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "jsonschema": ( - "https://python-jsonschema.readthedocs.io/en/latest/", None, + "https://python-jsonschema.readthedocs.io/en/latest/", + None, ), "packaging": ("https://packaging.python.org/", None), "python": ("https://docs.python.org/", None), "sphinx": ("https://www.sphinx-doc.org/en/master/", None), } -# -- Options for the linkcheck builder ------------------------------------ - - -def entire_domain(host): - return r"http.?://" + re.escape(host) + r"($|/.*)" +# -- sphinxcontrib-spelling -- - -linkcheck_ignore = [ - entire_domain("codecov.io"), - entire_domain("img.shields.io"), - "https://github.com/python-jsonschema/sphinx-json-schema-spec/actions", - "https://github.com/python-jsonschema/sphinx-json-schema-spec/workflows/CI/badge.svg", # noqa: E501 -] +spelling_word_list_filename = "spelling-wordlist.txt" +spelling_show_suggestions = True diff --git a/docs/requirements.in b/docs/requirements.in index cff356e..b1df148 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -1,4 +1,8 @@ -file:.#egg=sphinx_json_schema_spec +file:. furo sphinx>=5.1 sphinxcontrib-spelling>5 +sphinxext-opengraph + +# Until pyenchant/pyenchant#302 is released... +pyenchant>=3.3.0rc1 diff --git a/docs/requirements.txt b/docs/requirements.txt index b4d2626..04666da 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,73 +1,81 @@ -# -# This file is autogenerated by pip-compile with python 3.11 -# To update, run: -# -# pip-compile docs/requirements.in -# -alabaster==0.7.12 +# This file was autogenerated by uv via the following command: +# uv pip compile --output-file /Users/julian/Development/sphinx-json-schema-spec/docs/requirements.txt docs/requirements.in +accessible-pygments==0.0.5 + # via furo +alabaster==1.0.0 # via sphinx -babel==2.11.0 +babel==2.17.0 # via sphinx -beautifulsoup4==4.11.1 +beautifulsoup4==4.14.2 # via furo -certifi==2022.12.7 +certifi==2025.10.5 # via requests -charset-normalizer==2.1.1 +charset-normalizer==3.4.4 # via requests -docutils==0.19 +docutils==0.21.2 # via sphinx -furo==2022.12.7 +furo==2025.9.25 # via -r docs/requirements.in -idna==3.4 +idna==3.11 # via requests imagesize==1.4.1 # via sphinx -jinja2==3.1.2 +jinja2==3.1.6 # via sphinx -lxml==4.9.2 +lxml==6.0.2 # via sphinx-json-schema-spec -markupsafe==2.1.1 +markupsafe==3.0.3 # via jinja2 -packaging==22.0 +packaging==25.0 # via sphinx -pyenchant==3.2.2 - # via sphinxcontrib-spelling -pygments==2.13.0 +pyenchant==3.3.0 # via + # -r docs/requirements.in + # sphinxcontrib-spelling +pygments==2.19.2 + # via + # accessible-pygments # furo # sphinx -pytz==2022.6 - # via babel -requests==2.28.1 +requests==2.32.5 + # via + # sphinx + # sphinxcontrib-spelling +roman-numerals-py==3.1.0 # via sphinx -snowballstemmer==2.2.0 +snowballstemmer==3.0.1 # via sphinx -soupsieve==2.3.2.post1 +soupsieve==2.8 # via beautifulsoup4 -sphinx==5.3.0 +sphinx==8.2.3 # via # -r docs/requirements.in # furo # sphinx-basic-ng # sphinx-json-schema-spec # sphinxcontrib-spelling -sphinx-basic-ng==1.0.0b1 + # sphinxext-opengraph +sphinx-basic-ng==1.0.0b2 # via furo -file:.#egg=sphinx_json_schema_spec +sphinx-json-schema-spec @ file:. # via -r docs/requirements.in -sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-applehelp==2.0.0 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==2.0.0 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==2.0.0 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sphinxcontrib-spelling==7.7.0 +sphinxcontrib-spelling==8.0.1 # via -r docs/requirements.in -urllib3==1.26.13 +sphinxext-opengraph==0.13.0 + # via -r docs/requirements.in +typing-extensions==4.15.0 + # via beautifulsoup4 +urllib3==2.5.0 # via requests diff --git a/docs/spelling_wordlist.txt b/docs/spelling-wordlist.txt similarity index 100% rename from docs/spelling_wordlist.txt rename to docs/spelling-wordlist.txt diff --git a/noxfile.py b/noxfile.py index 2d3934d..f385768 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,62 +1,78 @@ from pathlib import Path +from tempfile import TemporaryDirectory import nox ROOT = Path(__file__).parent +PYPROJECT = ROOT / "pyproject.toml" DOCS = ROOT / "docs" PACKAGE = ROOT / "sphinx_json_schema_spec" +REQUIREMENTS = dict( + docs=DOCS / "requirements.txt", +) +REQUIREMENTS_IN = [ # this is actually ordered, as files depend on each other + (path.parent / f"{path.stem}.in", path) for path in REQUIREMENTS.values() +] + +SUPPORTED = ["3.11", "3.12", "3.13"] +LATEST = SUPPORTED[-1] +nox.options.default_venv_backend = "uv|virtualenv" nox.options.sessions = [] -def session(default=True, **kwargs): +def session(default=True, python=LATEST, **kwargs): # noqa: D103 def _session(fn): if default: nox.options.sessions.append(kwargs.get("name", fn.__name__)) - return nox.session(**kwargs)(fn) + return nox.session(python=python, **kwargs)(fn) return _session -@session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"]) +@session(python=SUPPORTED) def tests(session): - session.install("pytest", str(ROOT)) - session.run("pytest", "--verbosity=3") + """ + Run the test suite with a corresponding Python version. + """ + session.install("pytest", ROOT) + session.run("pytest", *session.posargs, PACKAGE) @session(tags=["build"]) def build(session): - session.install("build") - tmpdir = session.create_tmp() - session.run("python", "-m", "build", str(ROOT), "--outdir", tmpdir) - - -@session(tags=["style"]) -def readme(session): - session.install("build", "twine") - tmpdir = session.create_tmp() - session.run("python", "-m", "build", str(ROOT), "--outdir", tmpdir) - session.run("python", "-m", "twine", "check", tmpdir + "/*") + """ + Build a distribution suitable for PyPI and check its validity. + """ + session.install("build[uv]", "twine") + with TemporaryDirectory() as tmpdir: + session.run( + "pyproject-build", + "--installer=uv", + ROOT, + "--outdir", + tmpdir, + ) + session.run("twine", "check", "--strict", tmpdir + "/*") @session(tags=["style"]) def style(session): - session.install( - "flake8", - "flake8-broken-line", - "flake8-bugbear", - "flake8-commas", - "flake8-quotes", - "flake8-tidy-imports", - ) - session.run("python", "-m", "flake8", str(PACKAGE), __file__) + """ + Check Python code style. + """ + session.install("ruff") + session.run("ruff", "check", ROOT) @session() def typing(session): - session.install("mypy", "types-docutils", "types-lxml", str(ROOT)) - session.run("python", "-m", "mypy", str(PACKAGE)) + """ + Check static typing. + """ + session.install("mypy", "types-docutils", "types-lxml", ROOT) + session.run("python", "-m", "mypy", PACKAGE) @session(tags=["docs"]) @@ -74,28 +90,55 @@ def typing(session): ], ) def docs(session, builder): - session.install("-r", str(DOCS / "requirements.txt")) - tmpdir = Path(session.create_tmp()) - argv = ["-n", "-T", "-W"] - if builder != "spelling": - argv += ["-q"] - session.run( - "python", - "-m", - "sphinx", - "-b", - builder, - str(DOCS), - str(tmpdir / builder), - *argv, - ) + """ + Build the documentation using a specific Sphinx builder. + """ + session.install("-r", REQUIREMENTS["docs"]) + with TemporaryDirectory() as tmpdir_str: + tmpdir = Path(tmpdir_str) + argv = ["-n", "-T", "-W"] + if builder != "spelling": + argv += ["-q"] + posargs = session.posargs or [tmpdir / builder] + session.run( + "python", + "-m", + "sphinx", + "-b", + builder, + DOCS, + *argv, + *posargs, + ) @session(tags=["docs", "style"], name="docs(style)") def docs_style(session): + """ + Check the documentation style. + """ session.install( "doc8", "pygments", "pygments-github-lexers", ) - session.run("python", "-m", "doc8", "--max-line-length", "1000", str(DOCS)) + session.run("python", "-m", "doc8", "--config", PYPROJECT, DOCS) + + +@session(default=False) +def requirements(session): + """ + Update the project's pinned requirements. + + You should commit the result afterwards. + """ + if session.venv_backend == "uv": + cmd = ["uv", "pip", "compile"] + else: + session.install("pip-tools") + cmd = ["pip-compile", "--resolver", "backtracking", "--strip-extras"] + + for each, out in REQUIREMENTS_IN: + # otherwise output files end up with silly absolute path comments... + relative = each.relative_to(ROOT) + session.run(*cmd, "--upgrade", "--output-file", out, relative) diff --git a/pyproject.toml b/pyproject.toml index 45d9c5d..1fba154 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,49 +8,108 @@ source = "vcs" [project] name = "sphinx_json_schema_spec" description = "Sphinx support for the JSON Schema specifications" +requires-python = ">=3.11" readme = "README.rst" -requires-python = ">=3.7" -license = {text = "MIT"} +license = "MIT" +license-files = ["COPYING"] keywords = ["json schema", "jsonschema", "data validation", "sphinx", "json"] authors = [ - {email = "Julian+sphinx-json-schema-spec@GrayVines.com"}, - {name = "Julian Berman"}, + { name = "Julian Berman", email = "Julian+sphinx-json-schema-spec@GrayVines.com" }, ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", + "Programming Language :: Python", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", - "Programming Language :: Python", "Framework :: Sphinx :: Extension", "Topic :: Documentation :: Sphinx", + "Topic :: File Formats :: JSON :: JSON Schema", ] dynamic = ["version"] - dependencies = [ "lxml", "sphinx>=5.1.1", - "importlib_metadata;python_version<'3.8'", ] [project.urls] Homepage = "https://github.com/python-jsonschema/sphinx-json-schema-spec" +Documentation = "https://sphinx-json-schema-spec.readthedocs.io/" Issues = "https://github.com/python-jsonschema/sphinx-json-schema-spec/issues/" Funding = "https://github.com/sponsors/Julian" Source = "https://github.com/python-jsonschema/sphinx-json-schema-spec" [tool.doc8] -max-line-length=1000 +ignore = [ + "D000", # see PyCQA/doc8#125 + "D001", # one sentence per line, so max length doesn't make sense +] + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "A001", # It's fine to shadow builtins + "A002", + "A003", + "A005", + "ARG", # This is all wrong whenever an interface is involved + "ANN", # Just let the type checker do this + "B006", # Mutable arguments require care but are OK if you don't abuse them + "B008", # It's totally OK to call functions for default arguments. + "B904", # raise SomeException(...) is fine. + "B905", # No need for explicit strict, this is simply zip's default behavior + "C408", # Calling dict is fine when it saves quoting the keys + "C901", # Not really something to focus on + "D105", # It's fine to not have docstrings for magic methods. + "D107", # __init__ especially doesn't need a docstring + "D200", # This rule makes diffs uglier when expanding docstrings + "D203", # No blank lines before docstrings. + "D212", # Start docstrings on the second line. + "D400", # This rule misses sassy docstrings ending with ! or ? + "D401", # This rule is too flaky. + "D406", # Section headers should end with a colon not a newline + "D407", # Underlines aren't needed + "D412", # Plz spaces after section headers + "EM101", # These don't bother me, it's fine there's some duplication. + "EM102", + "FBT", # It's worth avoiding boolean args but I don't care to enforce it + "FIX", # Yes thanks, if I could it wouldn't be there + "N", # These naming rules are silly + "PLR0912", # These metrics are fine to be aware of but not to enforce + "PLR0913", + "PLR0915", + "PLW2901", # Shadowing for loop variables is occasionally fine. + "PT006", # pytest parametrize takes strings as well + "PYI025", # wat, I'm not confused, thanks. + "RET502", # Returning None implicitly is fine + "RET503", + "RET505", # These push you to use `if` instead of `elif`, but for no reason + "RET506", + "RSE102", # Ha, what, who even knew you could leave the parens off. But no. + "S", # Security warnings with lots of false positives, as usual + "SIM300", # Not sure what heuristic this uses, but it's easily incorrect + "SLF001", # Private usage within this package itself is fine + "TD", # These TODO style rules are also silly +] + +[tool.ruff.lint.flake8-pytest-style] +mark-parentheses = false + +[tool.ruff.lint.flake8-quotes] +docstring-quotes = "double" + +[tool.ruff.lint.isort] +combine-as-imports = true +from-first = true -[tool.isort] -from_first = true -include_trailing_comma = true -multi_line_output = 3 +[tool.ruff.lint.per-file-ignores] +"noxfile.py" = ["ANN", "D100", "S101", "T201"] +"docs/*" = ["ANN", "D", "INP001"] +"sphinx_json_schema_spec/tests/*" = ["ANN", "D", "RUF012", "S", "PLR", "TRY"] diff --git a/sphinx_json_schema_spec/__init__.py b/sphinx_json_schema_spec/__init__.py index 0a82c8c..85919a9 100644 --- a/sphinx_json_schema_spec/__init__.py +++ b/sphinx_json_schema_spec/__init__.py @@ -1,15 +1,15 @@ +""" +Sphinx support for interlinking to the JSON Schema specifications. +""" + from contextlib import suppress -from datetime import datetime +from datetime import UTC, datetime +from importlib import metadata from pathlib import Path from urllib.parse import urljoin import ssl import urllib.request -try: - from importlib import metadata -except ImportError: - import importlib_metadata as metadata # type: ignore - from docutils import nodes from lxml import html @@ -19,9 +19,9 @@ "validation": urljoin(BASE_URL, "json-schema-validation.html"), } HARDCODED = { - "$dynamicRef": "https://json-schema.org/draft/2020-12/json-schema-core.html#dynamic-ref", # noqa: E501 + "$dynamicRef": "https://json-schema.org/draft/2020-12/json-schema-core.html#dynamic-ref", "$ref": "https://json-schema.org/draft/2020-12/json-schema-core.html#ref", - "format": "https://json-schema.org/draft/2020-12/json-schema-validation.html#name-implementation-requirements", # noqa: E501 + "format": "https://json-schema.org/draft/2020-12/json-schema-validation.html#name-implementation-requirements", } @@ -34,8 +34,8 @@ def setup(app): app (sphinx.application.Sphinx): the Sphinx application context - """ + """ app.add_config_value("cache_path", "_cache", "") CACHE = Path(app.config.cache_path) @@ -69,13 +69,13 @@ def fetch_or_load(cache_path, url): url: the URL of the document - """ + """ version = metadata.version("sphinx-json-schema-spec") headers = {"User-Agent": f"sphinx-json-schema-spec v{version}"} with suppress(FileNotFoundError): - modified = datetime.utcfromtimestamp(cache_path.stat().st_mtime) + modified = datetime.fromtimestamp(cache_path.stat().st_mtime, UTC) date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC") headers["If-Modified-Since"] = date @@ -83,7 +83,7 @@ def fetch_or_load(cache_path, url): context = ssl.create_default_context() response = urllib.request.urlopen(request, context=context) - if response.code == 200: + if response.code == 200: # noqa: PLR2004 with cache_path.open("w+b") as out: out.writelines(response) out.seek(0) @@ -111,7 +111,7 @@ def keyword(name, raw_text, text, lineno, inliner): the name of the role in the document - raw_source (str): + raw_text (str): the raw text (role with argument) @@ -133,8 +133,8 @@ def keyword(name, raw_text, text, lineno, inliner): a 2-tuple of nodes to insert into the document and an iterable of system messages, both possibly empty - """ + """ hardcoded = HARDCODED.get(text) if hardcoded is not None: return [nodes.reference(raw_text, text, refuri=hardcoded)], [] @@ -153,9 +153,7 @@ def keyword(name, raw_text, text, lineno, inliner): uri = urljoin(vocabulary_url, header.find("a").attrib["href"]) break else: - inliner.reporter.warning( - "Didn't find a target for {0}".format(text), - ) + inliner.reporter.warning(f"Didn't find a target for {text}") uri = BASE_URL reference = nodes.reference(raw_text, text, refuri=uri) @@ -165,7 +163,9 @@ def keyword(name, raw_text, text, lineno, inliner): def missing_reference(glossary): - + """ + Get callbacked for Sphinx's missing reference hook. + """ terms = { link.lstrip("#") for _, _, link, _ in glossary.iterlinks() @@ -176,7 +176,6 @@ def _missing_reference(app, env, node, contnod): """ Resolve a reference to a JSON Schema Glossary term. """ - if node["reftype"] != "term": return @@ -188,4 +187,5 @@ def _missing_reference(app, env, node, contnod): text = contnod.astext() if node["refexplicit"] else target return nodes.reference(text, text, internal=False, refuri=uri) + return _missing_reference