From eb8fd91ceb4d9f6a2d405dafa0140b688e97ef57 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 4 Oct 2026 15:08:45 -0700 Subject: [PATCH 1/9] - Move linkcheck test to a separate environment so that py314 can be run separately --- tox.ini | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3875846..b620add 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ envlist = py312 py313 py314 + linkcheck [testenv] description = test environment @@ -14,12 +15,17 @@ dependency_groups = dev setenv = py310: HTTPDOMAIN_WHEEL_SMOKE=1 commands = - # test links for py314 only - py314: sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck # test html output sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html pytest +[testenv:linkcheck] +description = linkcheck +base_python = py314 +commands = + # check links + sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck + [testenv:py310-sphinx-version-smoke] description = minimal Sphinx 8.1.3 compatibility smoke test runner = uv-venv-runner From ce27542845daaa161a8b91d2c4286385bebd21cd Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 4 Oct 2026 15:09:41 -0700 Subject: [PATCH 2/9] - Rename test folder to tests and adjust configuration --- .github/workflows/tests.yml | 2 +- {test => tests}/Makefile | 0 {test => tests}/autoflask_alternative_lang.py | 0 {test => tests}/autoflask_methodview.py | 0 {test => tests}/autoflask_sampleapp.py | 0 {test => tests}/bottle_test.py | 0 {test => tests}/conf.py | 0 {test => tests}/index.rst | 0 {test => tests}/make.bat | 0 {test => tests}/noindex_test.py | 0 {test => tests}/sphinx_version_smoke.py | 0 tox.ini | 5 ++--- 12 files changed, 3 insertions(+), 4 deletions(-) rename {test => tests}/Makefile (100%) rename {test => tests}/autoflask_alternative_lang.py (100%) rename {test => tests}/autoflask_methodview.py (100%) rename {test => tests}/autoflask_sampleapp.py (100%) rename {test => tests}/bottle_test.py (100%) rename {test => tests}/conf.py (100%) rename {test => tests}/index.rst (100%) rename {test => tests}/make.bat (100%) rename {test => tests}/noindex_test.py (100%) rename {test => tests}/sphinx_version_smoke.py (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 207b2b0..2934449 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,5 +40,5 @@ jobs: - name: Install tox run: uv tool install tox --with tox-uv - - name: Test + - name: Tests run: tox r -e ${{ matrix.config[1] }} diff --git a/test/Makefile b/tests/Makefile similarity index 100% rename from test/Makefile rename to tests/Makefile diff --git a/test/autoflask_alternative_lang.py b/tests/autoflask_alternative_lang.py similarity index 100% rename from test/autoflask_alternative_lang.py rename to tests/autoflask_alternative_lang.py diff --git a/test/autoflask_methodview.py b/tests/autoflask_methodview.py similarity index 100% rename from test/autoflask_methodview.py rename to tests/autoflask_methodview.py diff --git a/test/autoflask_sampleapp.py b/tests/autoflask_sampleapp.py similarity index 100% rename from test/autoflask_sampleapp.py rename to tests/autoflask_sampleapp.py diff --git a/test/bottle_test.py b/tests/bottle_test.py similarity index 100% rename from test/bottle_test.py rename to tests/bottle_test.py diff --git a/test/conf.py b/tests/conf.py similarity index 100% rename from test/conf.py rename to tests/conf.py diff --git a/test/index.rst b/tests/index.rst similarity index 100% rename from test/index.rst rename to tests/index.rst diff --git a/test/make.bat b/tests/make.bat similarity index 100% rename from test/make.bat rename to tests/make.bat diff --git a/test/noindex_test.py b/tests/noindex_test.py similarity index 100% rename from test/noindex_test.py rename to tests/noindex_test.py diff --git a/test/sphinx_version_smoke.py b/tests/sphinx_version_smoke.py similarity index 100% rename from test/sphinx_version_smoke.py rename to tests/sphinx_version_smoke.py diff --git a/tox.ini b/tox.ini index b620add..09d7be2 100644 --- a/tox.ini +++ b/tox.ini @@ -28,10 +28,9 @@ commands = [testenv:py310-sphinx-version-smoke] description = minimal Sphinx 8.1.3 compatibility smoke test -runner = uv-venv-runner -basepython = python3.10 +base_python = py310 dependency_groups = deps = sphinx==8.1.3 commands = - python test/sphinx_version_smoke.py + python tests/sphinx_version_smoke.py From 8948406d18804dcafe30617fef1c29a4711804af Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 4 Oct 2026 15:09:55 -0700 Subject: [PATCH 3/9] Remove unused imports --- {test => tests}/websocket_test.py | 2 -- 1 file changed, 2 deletions(-) rename {test => tests}/websocket_test.py (99%) diff --git a/test/websocket_test.py b/tests/websocket_test.py similarity index 99% rename from test/websocket_test.py rename to tests/websocket_test.py index ef132c6..a11aebb 100644 --- a/test/websocket_test.py +++ b/tests/websocket_test.py @@ -5,11 +5,9 @@ import sys import textwrap import zipfile -from pathlib import Path import pytest -from docutils import nodes from sphinx import addnodes from sphinx.util.inventory import InventoryFile From df1b234f6ae89ef4954d5ca126db04af8080ce36 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:12:57 -0700 Subject: [PATCH 4/9] Add intersphinx to Sphinx configuration and clean up --- docs/conf.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ab4fd1b..36f5405 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,12 +65,16 @@ def get_version_from_toml() -> str: # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinxcontrib.httpdomain', 'sphinxcontrib.autohttp.flask', - 'sphinxcontrib.autohttp.flaskqref', - 'sphinxcontrib.autohttp.bottle', - 'sphinxcontrib.autohttp.tornado', - 'sphinx.ext.extlinks'] - +extensions = [ + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "sphinxcontrib.autohttp.bottle", + "sphinxcontrib.autohttp.flask", + "sphinxcontrib.autohttp.flaskqref", + "sphinxcontrib.autohttp.tornado", + "sphinxcontrib.httpdomain", +] + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -302,9 +306,26 @@ def get_version_from_toml() -> str: # Suppress nonlocal image warning suppress_warnings = ['image.nonlocal_uri'] +# -- linkcheck builder configuration ---------------------------------- linkcheck_ignore = [ # Don't check old links to Bitbucket - r'^https://bitbucket\.org/birkenfeld/sphinx-contrib/(issues|pull-requests)/\d+/', # noqa: E501 + r"^https://bitbucket\.org/birkenfeld/sphinx-contrib/(issues|pull-requests)/\d+/", # noqa: E501 # The following site has imposed 403 forbidden responses, link is valid - 'https://datatracker.ietf.org/doc/html/', + "https://datatracker.ietf.org/doc/html/", ] + + +# -- sphinx.ext.intersphinx configuration ---------------------------------- + +# This extension can generate automatic links to the documentation of objects +# in other projects. Usage is simple: whenever Sphinx encounters a +# cross-reference that has no matching target in the current documentation set, +# it looks for targets in the documentation sets configured in +# intersphinx_mapping. A reference like :py:class:`zipfile.ZipFile` can then +# linkto the Python documentation for the ZipFile class, without you having to +# specify where it is located exactly. +# +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html +intersphinx_mapping = { + "sphinx": ("https://www.sphinx-doc.org/en/master/", None), +} From 48291a42107a4467ce31caf8c3b0e557c25b69c2 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:14:40 -0700 Subject: [PATCH 5/9] - Improve documentation of JSON fields - Add tests for JSON fields - Fix the direction of the response and request indicators for headers and JSON fields --- docs/index.rst | 177 +++++++++++++++++------ src/sphinxcontrib/httpdomain/__init__.py | 20 +-- tests/json_fields_test.py | 116 +++++++++++++++ 3 files changed, 262 insertions(+), 51 deletions(-) create mode 100644 tests/json_fields_test.py diff --git a/docs/index.rst b/docs/index.rst index 9292136..0f83760 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -331,28 +331,58 @@ Additionally, you may specify custom options to the directives: Resource Fields --------------- -Inside HTTP resource description directives like :rst:dir:`get`, -reStructuredText field lists with these fields are recognized and formatted -nicely: +Inside HTTP resource description directives such as :rst:dir:`get`, reStructuredText :doc:`field lists ` for the following fields are recognized and nicely formatted. ``param``, ``parameter``, ``arg``, ``argument`` Description of URL parameter. + Example: + + .. sourcecode:: rst + + .. http:post:: /users/(int:user_id)/add/ + + :param title: the post title + :parameter body: the post body + :arg sticky: whether it's sticky or not + :argument publish: publication date + + Rendering: + + .. http:post:: /users/(int:user_id)/add/ + + :param title: the post title + :parameter body: the post body + :arg sticky: whether it's sticky or not + :argument publish: publication date + ``queryparameter``, ``queryparam``, ``qparam``, ``query`` Description of parameter passed by request query string. - It optionally can be typed, all the query parameters will have obviously - string types though. But it's useful if there's conventions for it. + It can be typed. + Although all the query parameters will be string types, it's useful if there are conventions for it. .. versionchanged:: 1.1.9 - It can be typed e.g.: + It can be typed, for example: - .. sourcecode:: rst + .. sourcecode:: rst + + .. http:get:: /users/(int:user_id)/posts/view + + :param string title: the post title + :parameter string body: the post body + :arg boolean sticky: whether it's sticky or not + :argument date publish: publication date + + Rendering: - :query string title: the post title - :query string body: the post body - :query boolean sticky: whether it's sticky or not + .. http:get:: /users/(int:user_id)/posts/view + + :param string title: the post title + :parameter string body: the post body + :arg boolean sticky: whether it's sticky or not + :argument date publish: publication date ``formparameter``, ``formparam``, ``fparam``, ``form`` Description of parameter passed by request content body, encoded in @@ -364,14 +394,15 @@ nicely: :mimetype:`application/json`. .. deprecated:: 1.3.0 - Use ``reqjsonobj``/``reqjson``/``jsonobj``/``>json`` and + ``reqjsonarr``/``>jsonarr`` instead. .. versionadded:: 1.1.8 .. versionchanged:: 1.1.9 - It can be typed e.g.: + It can be typed, for example: .. sourcecode:: rst @@ -379,56 +410,120 @@ nicely: :jsonparam string body: the post body :jsonparam boolean sticky: whether it's sticky or not -``reqjsonobj``, ``reqjson``, ``jsonobj``, ``>json`` + Description of a single field of a JSON object passed in the request body, and encoded in :mimetype:`application/json`. + The key difference from ``json`` is that the request is explicitly defined in the described object. - .. sourcecode:: rst + Using the following JSON object, it can be typed as shown in the subsequent example. + + .. code-block:: json + + { + "user_id": 1, + "post_id": 2 + } + + .. code-block:: rst + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :reqjson integer user_id: The user ID. + :>json integer post_id: The post ID. + + Rendering: + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :reqjson integer user_id: The user ID. + :>json integer post_id: The post ID. - :json >jsonobj post_id: The post ID. + + Rendering: + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :reqjson reqjsonobj user_id: The user ID. + :>json >jsonobj post_id: The post ID. .. versionadded:: 1.3.0 -``resjsonobj``, ``resjson``, ``>jsonobj``, ``>json`` - Description of a single field of JSON object returned with response body, - encoded in :mimetype:`application/json`. +``resjsonobj``, ``resjson``, ``json boolean ok: Operation status + :resjson boolean ok: Operation status. + :jsonarr`` +``reqjsonarr``, ``>jsonarr``, +``resjsonarr``, ``json`` respectively, but uses for describing - objects schema inside of returned array. + Similar to ``>json`` and ``jsonarr boolean ok: Operation status. Not present in case of error - :>jsonarr string id: Object ID - :>jsonarr string error: Error type - :>jsonarr string reason: Error reason + :json boolean status: Operation status - ``requestheader``, ``reqheader``, ``>header`` Description of request header field. diff --git a/src/sphinxcontrib/httpdomain/__init__.py b/src/sphinxcontrib/httpdomain/__init__.py index 3c2e4ef..da0f5c9 100644 --- a/src/sphinxcontrib/httpdomain/__init__.py +++ b/src/sphinxcontrib/httpdomain/__init__.py @@ -277,17 +277,17 @@ class HTTPResource(ObjectDescription): names=('jsonparameter', 'jsonparam', 'json'), typenames=('jsonparamtype', 'jsontype')), TypedField('requestjsonobject', label=_('Request JSON Object'), - names=('reqjsonobj', 'reqjson', 'jsonobj', '>json'), + typenames=('reqjsonobj', '>jsonobj')), TypedField('requestjsonarray', label=_('Request JSON Array of Objects'), - names=('reqjsonarr', 'jsonarr'), + typenames=('reqjsonarrtype', '>jsonarrtype')), TypedField('responsejsonobject', label=_('Response JSON Object'), - names=('resjsonobj', 'resjson', '>jsonobj', '>json'), - typenames=('resjsonobj', '>jsonobj')), + names=('resjsonobj', 'resjson', 'jsonarr'), - typenames=('resjsonarrtype', '>jsonarrtype')), + names=('resjsonarr', 'header', 'reqheader', 'requestheader')), GroupedField('responseheader', label=_('Response Headers'), rolename='header', - names=('>header', 'resheader', 'responseheader')), + names=(' None: + source = write_project(tmp_path, { + "index.rst": """ + JSON fields + =========== + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :reqjson integer user_id: The user ID 1. + :>json string post_id: The post ID 1. + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :reqjson reqjsonobj user_id: The user ID 2. + :>json >jsonobj post_id: The post ID 2. + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :resjson boolean ok: Operation status 1. + :jsonarr integer publish: Date published. + :>jsonarr >jsonarrtype sticky: Whether the post is sticky. + + .. http:get:: /users/(int:user_id)/posts/(int:post_id) + + :resjsonarr boolean ok: Operation status. Not present in case of error + :resjsonarr resjsonarrtype id: Object ID + :user_id (integer) – The user ID 1." in index + assert "post_id (string) – The post ID 1." in index + + assert "user_id (reqjsonobj) – The user ID 2." in index + assert "post_id (>jsonobj) – The post ID 2." in index + + assert "Response JSON Object" in index + assert "ok (boolean) – Operation status 1." in index + assert "error (integer) – Error code 1." in index + + assert "ok (resjsonobj) – Operation status 2." in index + assert "error (<jsonobj) – Error code 2." in index + + assert "Request JSON Array of Objects" in index + assert "user_id (integer) – The user ID 1." in index + assert "post_id (reqjsonarrtype) – The post ID 1." in index + assert "publish (integer) – Date published." in index + assert "sticky (>jsonarrtype) – Whether the post is sticky." in index + + assert "Response JSON Array of Objects" in index + assert "ok (boolean) – Operation status. Not present in case of error" in index + assert "id (resjsonarrtype) – Object ID" in index + assert "error (string) – Error type" in index + assert "reason (<jsonarrtype) – Error reason" in index From 04f64cd50ec63b720418f78fab42026c5e52491f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:17:06 -0700 Subject: [PATCH 6/9] Change log --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1d3fd1e..e6e9d9b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ Major changes ............. - Keep the per-page signature anchor and permalink when ``:noindex:`` is set; only the global route registration is skipped. [:pull:`112` by @ggiesen] +- Fixed the direction of the response and request indicators for headers and JSON fields. Added documentation and tests for JSON fields. Added intersphinx to Sphinx configuration. [:issue:`24` by @stevepiercy] Minor changes From c70671aee0d3822d2f27951f0bdeae2a4d34a89f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:20:05 -0700 Subject: [PATCH 7/9] Change log --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e6e9d9b..c82886d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,6 +47,7 @@ Minor changes - Bumped tornado from 6.5.8 to 6.5.10. [:pull:`134`] - Bumped uv-build requirement from >=0.12.16,<0.13.0 to >=0.12.19,<0.13.0. [:pull:`138`] - Updated the smoke test for Sphinx 8.1.3. [:pull:`128` by @stevepiercy] +- Moved linkcheck test to a separate environment so that the ``py314`` environment can be run separately. Refactored :file:`test` folder to :file:`tests`. Removed unused imports from :file:`websocket_test.py`. Added intersphinx to Sphinx configuration. [:pull:`139` by @stevepiercy] Version 2.0.0 From 87b29c9685f549582e9e3352cfaaf139ad87ef9a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:28:09 -0700 Subject: [PATCH 8/9] Run linkcheck in tests --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2934449..8128ce8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,6 +23,7 @@ jobs: - ["3.12", "py312"] - ["3.13", "py313"] - ["3.14", "py314"] + - ["3.14", "linkcheck"] runs-on: ${{ matrix.os[1] }} name: ${{ matrix.config[1] }} From 3179bd2bbcf1d3c76675c8583c3a11fbff18f405 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 5 Oct 2026 03:29:32 -0700 Subject: [PATCH 9/9] Fix PR number --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c82886d..60e169a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,7 +47,7 @@ Minor changes - Bumped tornado from 6.5.8 to 6.5.10. [:pull:`134`] - Bumped uv-build requirement from >=0.12.16,<0.13.0 to >=0.12.19,<0.13.0. [:pull:`138`] - Updated the smoke test for Sphinx 8.1.3. [:pull:`128` by @stevepiercy] -- Moved linkcheck test to a separate environment so that the ``py314`` environment can be run separately. Refactored :file:`test` folder to :file:`tests`. Removed unused imports from :file:`websocket_test.py`. Added intersphinx to Sphinx configuration. [:pull:`139` by @stevepiercy] +- Moved linkcheck test to a separate environment so that the ``py314`` environment can be run separately. Refactored :file:`test` folder to :file:`tests`. Removed unused imports from :file:`websocket_test.py`. Added intersphinx to Sphinx configuration. [:pull:`140` by @stevepiercy] Version 2.0.0