From 18ed8a233ba42978018dd1ffe1047b66c62e1cba Mon Sep 17 00:00:00 2001 From: Douglas Strodtman Date: Thu, 13 Aug 2026 10:07:49 -0400 Subject: [PATCH 1/2] Fix #171 Close the request body's sourcecode block with a blank line The old renderer emitted the `.. sourcecode:: json` directive for a request body and then the next `:status ...:` field with no blank line between them, so docutils reported explicit markup ending without a blank line and the build failed under -W. The blank line goes after the loop over the body's lines, mirroring the response example path. A commented-out `yield ''` was already sitting inside the loop, where it would have separated every line of JSON instead of closing the block. Signed-off-by: Douglas Strodtman --- sphinxcontrib/openapi/openapi30.py | 2 +- tests/test_openapi.py | 49 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/sphinxcontrib/openapi/openapi30.py b/sphinxcontrib/openapi/openapi30.py index 3b47277..25579b1 100644 --- a/sphinxcontrib/openapi/openapi30.py +++ b/sphinxcontrib/openapi/openapi30.py @@ -313,7 +313,7 @@ def _httpresource(endpoint, method, properties, convert, render_examples, for line in req_properties.splitlines(): # yield indent + line yield '{indent}{indent}{line}'.format(**locals()) - # yield '' + yield '' # print request example if render_examples: diff --git a/tests/test_openapi.py b/tests/test_openapi.py index d254572..bb7d8ac 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -724,6 +724,55 @@ def test_basic(self): Last known resource ETag. ''').lstrip() + def test_request_body(self): + renderer = renderers.HttpdomainOldRenderer(None, {'request': True}) + text = '\n'.join(renderer.render_restructuredtext_markup({ + 'openapi': '3.0.0', + 'paths': { + '/things': { + 'post': { + 'summary': 'Create Thing', + 'requestBody': { + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + }, + }, + }, + }, + }, + 'responses': { + '200': { + 'description': 'A thing created.', + }, + }, + }, + }, + }, + })) + assert text == textwrap.dedent(''' + .. http:post:: /things + :synopsis: Create Thing + + **Create Thing** + + **Request body:** + + .. sourcecode:: json + + { + "name":{ + "type":"string" + } + } + + :status 200: + A thing created. + ''').lstrip() + def test_rfc7807(self): # Fix order to have a reliable test pb_example = collections.OrderedDict() From 5af2f7c52700c1798c1bbf4eba9ccba8b079a5c9 Mon Sep 17 00:00:00 2001 From: Douglas Strodtman Date: Thu, 13 Aug 2026 11:57:29 -0400 Subject: [PATCH 2/2] Close the same sourcecode block in the 3.1 renderer The 3.0 and 3.1 renderers carry separate copies of the request body path, and both were missing the blank line, so fixing only the 3.0 one left every 3.1 spec using :request: failing under -W for the same reason. Signed-off-by: Douglas Strodtman --- sphinxcontrib/openapi/openapi31.py | 2 +- tests/test_openapi.py | 52 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/sphinxcontrib/openapi/openapi31.py b/sphinxcontrib/openapi/openapi31.py index 0e8cba7..f24b61e 100644 --- a/sphinxcontrib/openapi/openapi31.py +++ b/sphinxcontrib/openapi/openapi31.py @@ -358,7 +358,7 @@ def _get_type_from_schema(schema): for line in req_properties.splitlines(): # yield indent + line yield "{indent}{indent}{line}".format(**locals()) - # yield '' + yield "" # print request example if render_examples: diff --git a/tests/test_openapi.py b/tests/test_openapi.py index bb7d8ac..e2c2b83 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1743,6 +1743,58 @@ def test_method_option(self): ''').lstrip() +class TestOpenApi31HttpDomain(object): + + def test_request_body(self): + renderer = renderers.HttpdomainOldRenderer(None, {'request': True}) + text = '\n'.join(renderer.render_restructuredtext_markup({ + 'openapi': '3.1.0', + 'paths': { + '/things': { + 'post': { + 'summary': 'Create Thing', + 'requestBody': { + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + }, + }, + }, + }, + }, + 'responses': { + '200': { + 'description': 'A thing created.', + }, + }, + }, + }, + }, + })) + assert text == textwrap.dedent(''' + .. http:post:: /things + :synopsis: Create Thing + + **Create Thing** + + **Request body:** + + .. sourcecode:: json + + { + "name":{ + "type":"string" + } + } + + :status 200: + A thing created. + ''').lstrip() + + class TestResolveRefs(object): def test_ref_resolving(self):