essentials-openapi 1.3.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/CHANGELOG.md +27 -0
  2. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/PKG-INFO +1 -1
  3. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/__init__.py +1 -1
  4. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/common.py +6 -0
  5. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/common.py +18 -4
  6. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/jinja.py +53 -0
  7. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/md.py +1 -1
  8. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/__init__.py +165 -2
  9. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/examples.py +13 -6
  10. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/components-responses.html +1 -1
  11. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/content-examples.html +1 -1
  12. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/external-docs.html +1 -1
  13. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/info.html +1 -1
  14. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/path-items.html +2 -2
  15. essentials_openapi-1.4.0/openapidocs/mk/v3/views_markdown/partial/request-parameters.html +9 -0
  16. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/request-responses.html +1 -1
  17. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/schema-repr.html +2 -4
  18. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/type.html +4 -4
  19. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/components-responses.html +1 -1
  20. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/external-docs.html +1 -1
  21. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/info.html +1 -1
  22. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/path-items.html +2 -2
  23. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/request-parameters.html +2 -2
  24. essentials_openapi-1.4.0/openapidocs/mk/v3/views_mkdocs/partial/request-responses.html +42 -0
  25. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/schema-repr.html +2 -4
  26. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/type.html +3 -1
  27. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_api/partial/schema-repr.html +1 -1
  28. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_schemas/partial/schema-repr.html +1 -1
  29. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/v3.py +39 -6
  30. essentials_openapi-1.3.0/openapidocs/mk/v3/views_markdown/partial/request-parameters.html +0 -9
  31. essentials_openapi-1.3.0/openapidocs/mk/v3/views_mkdocs/partial/request-responses.html +0 -63
  32. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/.gitignore +0 -0
  33. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/LICENSE +0 -0
  34. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/README.md +0 -0
  35. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/commands/__init__.py +0 -0
  36. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/commands/docs.py +0 -0
  37. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/logs.py +0 -0
  38. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/main.py +0 -0
  39. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/__init__.py +0 -0
  40. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/contents.py +0 -0
  41. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/generate.py +0 -0
  42. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/texts.py +0 -0
  43. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/README.md +0 -0
  44. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/__init__.py +0 -0
  45. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/layout.html +0 -0
  46. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/components-parameters.html +0 -0
  47. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/components-schemas.html +0 -0
  48. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/components-security-schemes.html +0 -0
  49. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/request-auth.html +0 -0
  50. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/request-body.html +0 -0
  51. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/servers.html +0 -0
  52. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_markdown/partial/tags.html +0 -0
  53. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/README.md +0 -0
  54. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/__init__.py +0 -0
  55. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/layout.html +0 -0
  56. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/components-parameters.html +0 -0
  57. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/components-schemas.html +0 -0
  58. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/components-security-schemes.html +0 -0
  59. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/content-examples.html +0 -0
  60. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/request-auth.html +0 -0
  61. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/request-body.html +0 -0
  62. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/servers.html +0 -0
  63. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_mkdocs/partial/tags.html +0 -0
  64. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_api/README.md +0 -0
  65. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_api/layout.html +0 -0
  66. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_schemas/README.md +0 -0
  67. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/mk/v3/views_plantuml_schemas/layout.html +0 -0
  68. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/utils/__init__.py +0 -0
  69. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/utils/source.py +0 -0
  70. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/utils/web.py +0 -0
  71. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/openapidocs/v2.py +0 -0
  72. {essentials_openapi-1.3.0 → essentials_openapi-1.4.0}/pyproject.toml +0 -0
@@ -5,7 +5,34 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.0] - 2026-03-07 :copilot:
9
+
10
+ - Add OAS 3.1 support, cross-version warnings, and fix nullable spacing, by @dcode.
11
+ - Fix MARKDOWN style table separators to use minimum 3 hyphens (issue #39), reported by @michael-nok.
12
+ - Fix [#45](https://github.com/Neoteroi/essentials-openapi/issues/45): add support for displaying descriptions of schema properties, reported by @Maia-Everett.
13
+ - Fix [#30](https://github.com/Neoteroi/essentials-openapi/issues/30): raise an error
14
+ when trying to generate output from an older Swagger v2 specification file (these were
15
+ never supported as there was never a `/mk/v2/` namespace, intentionally).
16
+ - Fix [#35](https://github.com/Neoteroi/essentials-openapi/issues/35): group response
17
+ codes in a tab group on MkDocs output, reported by @Andre601.
18
+ - Fix [#47](https://github.com/Neoteroi/essentials-openapi/issues/47): remove `wordwrap`
19
+ filters from all templates as they break links and mermaid chart code blocks in
20
+ descriptions, reported by @ElementalWarrior.
21
+ - Fix [#49](https://github.com/Neoteroi/essentials-openapi/issues/49): support `$ref`
22
+ values of the form `file.yaml#/fragment/path` (external file with JSON Pointer
23
+ fragment), reported by @mbklein.
24
+ - Fix [#55](https://github.com/Neoteroi/essentials-openapi/issues/55): `jsonSchemaDialect`
25
+ is not required and should not have a default value.
26
+ - Fix [#60](https://github.com/Neoteroi/essentials-openapi/issues/60): resolve `$ref`
27
+ values in response headers pointing to `#/components/headers/...` to avoid
28
+ `UndefinedError` when rendering response tables, reported by @copiousfreetime.
29
+ - Fix [#64](https://github.com/Neoteroi/essentials-openapi/issues/64): use `examples`
30
+ array (JSON Schema draft 6+) as a fallback for auto-generated response examples;
31
+ also use `enum` values as examples for all scalar types (integer, number, boolean),
32
+ reported by @jan-ldwg.
33
+
8
34
  ## [1.3.0] - 2025-11-19
35
+
9
36
  - Add support for passing custom Jinja2 templates as an argument, by @sindrehan.
10
37
 
11
38
  ## [1.2.1] - 2025-07-30
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: essentials-openapi
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Classes to generate OpenAPI Documentation v3 and v2, in JSON and YAML.
5
5
  Project-URL: Homepage, https://github.com/Neoteroi/essentials-openapi
6
6
  Project-URL: Bug Tracker, https://github.com/Neoteroi/essentials-openapi/issues
@@ -1,2 +1,2 @@
1
- __version__ = "1.3.0"
1
+ __version__ = "1.4.0"
2
2
  VERSION = __version__
@@ -82,6 +82,10 @@ def normalize_dict_factory(items: list[tuple[Any, Any]]) -> dict[str, Any]:
82
82
  data["$ref"] = value
83
83
  continue
84
84
 
85
+ if key == "defs":
86
+ data["$defs"] = value
87
+ continue
88
+
85
89
  for handler in TYPES_HANDLERS:
86
90
  value = handler.normalize(value)
87
91
 
@@ -129,6 +133,8 @@ def _asdict_inner(obj: Any, dict_factory: Callable[[Any], Any]) -> Any:
129
133
  for k, v in obj.items()
130
134
  )
131
135
  else:
136
+ for handler in TYPES_HANDLERS:
137
+ obj = handler.normalize(obj)
132
138
  return copy.deepcopy(obj)
133
139
 
134
140
 
@@ -33,17 +33,29 @@ def is_reference(data: object) -> bool:
33
33
  return "$ref" in data
34
34
 
35
35
 
36
+ def _type_matches(type_val: Any, expected: str) -> bool:
37
+ """
38
+ Returns True if type_val equals expected (OAS 3.0 string) or contains expected
39
+ (OAS 3.1 list).
40
+ """
41
+ if isinstance(type_val, list):
42
+ return expected in type_val
43
+ return type_val == expected
44
+
45
+
36
46
  def is_object_schema(data: object) -> bool:
37
47
  """
38
48
  Returns a value indicating whether the given schema dictionary represents
39
49
  an object schema.
40
50
 
41
- is_reference({"type": "array", "items": {...}}) -> True
51
+ Supports both OAS 3.0 (type: "object") and OAS 3.1 (type: ["object", ...]).
42
52
  """
43
53
  if not isinstance(data, dict):
44
54
  return False
45
55
  data = cast(dict[str, object], data)
46
- return data.get("type") == "object" and isinstance(data.get("properties"), dict)
56
+ return _type_matches(data.get("type"), "object") and isinstance(
57
+ data.get("properties"), dict
58
+ )
47
59
 
48
60
 
49
61
  def is_array_schema(data: object) -> bool:
@@ -51,12 +63,14 @@ def is_array_schema(data: object) -> bool:
51
63
  Returns a value indicating whether the given schema dictionary represents
52
64
  an array schema.
53
65
 
54
- is_reference({"type": "array", "items": {...}}) -> True
66
+ Supports both OAS 3.0 (type: "array") and OAS 3.1 (type: ["array", ...]).
55
67
  """
56
68
  if not isinstance(data, dict):
57
69
  return False
58
70
  data = cast(dict[str, object], data)
59
- return data.get("type") == "array" and isinstance(data.get("items"), dict)
71
+ return _type_matches(data.get("type"), "array") and isinstance(
72
+ data.get("items"), dict
73
+ )
60
74
 
61
75
 
62
76
  def get_ref_type_name(reference: dict[str, str] | str) -> str:
@@ -21,6 +21,56 @@ from .common import DocumentsWriter, is_reference
21
21
  from .md import normalize_link, write_table
22
22
 
23
23
 
24
+ def get_primary_type(type_val):
25
+ """
26
+ Returns the primary (first non-null) type from a schema type value.
27
+
28
+ Handles both OAS 3.0 (string) and OAS 3.1 (list) type representations:
29
+ - "string" → "string"
30
+ - ["string", "null"] → "string"
31
+ - ["null"] → "null"
32
+ - ["string", "integer"] → "string"
33
+ """
34
+ if not type_val:
35
+ return None
36
+ if isinstance(type_val, list):
37
+ non_null = [t for t in type_val if t != "null"]
38
+ return non_null[0] if non_null else "null"
39
+ return type_val
40
+
41
+
42
+ def is_nullable_schema(schema) -> bool:
43
+ """
44
+ Returns True if the given schema is nullable.
45
+
46
+ Handles both OAS 3.0 (nullable: true) and OAS 3.1 (type: [..., "null"]) patterns.
47
+ """
48
+ if not isinstance(schema, dict):
49
+ return False
50
+ if schema.get("nullable"):
51
+ return True
52
+ type_val = schema.get("type")
53
+ if isinstance(type_val, list):
54
+ return "null" in type_val
55
+ return False
56
+
57
+
58
+ def get_type_display(type_val) -> str:
59
+ """
60
+ Returns a display string for a schema type value.
61
+
62
+ Handles both OAS 3.0 (string) and OAS 3.1 (list) type representations:
63
+ - "string" → "string"
64
+ - ["string", "null"] → "string | null"
65
+ - ["string", "integer"] → "string | integer"
66
+ """
67
+ if not type_val:
68
+ return ""
69
+ if isinstance(type_val, list):
70
+ return " | ".join(str(t) for t in type_val)
71
+ return str(type_val)
72
+
73
+
24
74
  def configure_filters(env: Environment):
25
75
  env.filters.update(
26
76
  {"route": highlight_params, "table": write_table, "link": normalize_link}
@@ -35,6 +85,9 @@ def configure_functions(env: Environment):
35
85
  "scalar_types": {"string", "integer", "boolean", "number"},
36
86
  "get_http_status_phrase": get_http_status_phrase,
37
87
  "write_md_table": write_table,
88
+ "get_primary_type": get_primary_type,
89
+ "is_nullable_schema": is_nullable_schema,
90
+ "get_type_display": get_type_display,
38
91
  }
39
92
 
40
93
  env.globals.update(helpers)
@@ -49,7 +49,7 @@ def write_table_lines(
49
49
  if write_headers:
50
50
  # add separator line after headers
51
51
  yield write_row(
52
- ["-" * column_len for column_len in columns_widths.values()],
52
+ ["-" * max(3, column_len) for column_len in columns_widths.values()],
53
53
  columns_widths,
54
54
  padding,
55
55
  indent,
@@ -25,6 +25,19 @@ from openapidocs.mk.texts import EnglishTexts, Texts
25
25
  from openapidocs.mk.v3.examples import get_example_from_schema
26
26
  from openapidocs.utils.source import read_from_source
27
27
 
28
+ _OAS31_KEYWORDS = frozenset(
29
+ {
30
+ "const",
31
+ "if",
32
+ "then",
33
+ "else",
34
+ "prefixItems",
35
+ "unevaluatedProperties",
36
+ "unevaluatedItems",
37
+ "$defs",
38
+ }
39
+ )
40
+
28
41
 
29
42
  def _can_simplify_json(content_type) -> bool:
30
43
  return "json" in content_type or content_type == "text/plain"
@@ -106,11 +119,110 @@ class OpenAPIV3DocumentationHandler:
106
119
  custom_templates_path=templates_path,
107
120
  )
108
121
  self.doc = self.normalize_data(copy.deepcopy(doc))
122
+ self._warn_31_features_in_30_doc()
123
+ self._warn_30_features_in_31_doc()
109
124
 
110
125
  @property
111
126
  def source(self) -> str:
112
127
  return self._source
113
128
 
129
+ def _collect_31_features(self, obj: object, found: set) -> None:
130
+ """Recursively scans obj for OAS 3.1-specific features, collecting them in found."""
131
+ if not isinstance(obj, dict):
132
+ return
133
+
134
+ type_val = obj.get("type")
135
+ if isinstance(type_val, list):
136
+ found.add('type as list (e.g. ["string", "null"])')
137
+
138
+ for kw in _OAS31_KEYWORDS:
139
+ if kw in obj:
140
+ found.add(kw)
141
+
142
+ for kw in ("exclusiveMinimum", "exclusiveMaximum"):
143
+ val = obj.get(kw)
144
+ if (
145
+ val is not None
146
+ and isinstance(val, (int, float))
147
+ and not isinstance(val, bool)
148
+ ):
149
+ found.add(f"{kw} as number")
150
+
151
+ for value in obj.values():
152
+ if isinstance(value, dict):
153
+ self._collect_31_features(value, found)
154
+ elif isinstance(value, list):
155
+ for item in value:
156
+ self._collect_31_features(item, found)
157
+
158
+ def _warn_31_features_in_30_doc(self) -> None:
159
+ """
160
+ Emits a warning if OAS 3.1-specific features are detected in a document
161
+ that declares an OAS 3.0.x version.
162
+ """
163
+ version = self.doc.get("openapi", "")
164
+ if not (isinstance(version, str) and version.startswith("3.0")):
165
+ return
166
+
167
+ found: set = set()
168
+
169
+ if "webhooks" in self.doc:
170
+ found.add("webhooks")
171
+
172
+ self._collect_31_features(self.doc, found)
173
+
174
+ if found:
175
+ feature_list = ", ".join(sorted(found))
176
+ warnings.warn(
177
+ f"OpenAPI document declares version {version!r} but uses "
178
+ f"OAS 3.1-specific features: {feature_list}. "
179
+ "Consider updating the `openapi` field to '3.1.0'.",
180
+ stacklevel=3,
181
+ )
182
+
183
+ def _collect_30_features(self, obj: object, found: set) -> None:
184
+ """Recursively scans obj for OAS 3.0-specific features, collecting them in found."""
185
+ if not isinstance(obj, dict):
186
+ return
187
+
188
+ # nullable: true is OAS 3.0 only — replaced by type: [..., "null"] in 3.1
189
+ if obj.get("nullable") is True:
190
+ found.add("nullable: true")
191
+
192
+ # boolean exclusiveMinimum/exclusiveMaximum are 3.0 semantics;
193
+ # in 3.1 they are numeric bounds
194
+ for kw in ("exclusiveMinimum", "exclusiveMaximum"):
195
+ if isinstance(obj.get(kw), bool):
196
+ found.add(f"{kw}: true/false (boolean)")
197
+
198
+ for value in obj.values():
199
+ if isinstance(value, dict):
200
+ self._collect_30_features(value, found)
201
+ elif isinstance(value, list):
202
+ for item in value:
203
+ self._collect_30_features(item, found)
204
+
205
+ def _warn_30_features_in_31_doc(self) -> None:
206
+ """
207
+ Emits a warning if OAS 3.0-specific features are detected in a document
208
+ that declares an OAS 3.1.x version.
209
+ """
210
+ version = self.doc.get("openapi", "")
211
+ if not (isinstance(version, str) and version.startswith("3.1")):
212
+ return
213
+
214
+ found: set = set()
215
+ self._collect_30_features(self.doc, found)
216
+
217
+ if found:
218
+ feature_list = ", ".join(sorted(found))
219
+ warnings.warn(
220
+ f"OpenAPI document declares version {version!r} but uses "
221
+ f"OAS 3.0-specific features: {feature_list}. "
222
+ "These features are not valid in OAS 3.1 and may be ignored by tooling.",
223
+ stacklevel=3,
224
+ )
225
+
114
226
  def normalize_data(self, data):
115
227
  """
116
228
  Applies corrections to the OpenAPI specification, to simplify its handling.
@@ -125,6 +237,13 @@ class OpenAPIV3DocumentationHandler:
125
237
  $ref fields MUST be used in the specification to reference those parts as
126
238
  follows from the JSON Schema definitions.
127
239
  """
240
+ if isinstance(data.get("swagger"), str) and data["swagger"].startswith("2"):
241
+ raise ValueError(
242
+ "Swagger 2.0 specifications are not supported. "
243
+ "Please convert your specification to OpenAPI 3.x first. "
244
+ "You can use the online converter at https://converter.swagger.io/"
245
+ )
246
+
128
247
  if "components" not in data:
129
248
  data["components"] = {}
130
249
 
@@ -156,18 +275,46 @@ class OpenAPIV3DocumentationHandler:
156
275
  Handles a dictionary containing a $ref property, resolving the reference if it
157
276
  is to a file. This is used to read specification files when they are split into
158
277
  multiple items.
278
+
279
+ Supports three forms:
280
+ - ``#/internal/ref`` — internal ref, left as-is
281
+ - ``path/to/file.yaml`` — entire external file
282
+ - ``path/to/file.yaml#/fragment/path`` — fragment within an external file
159
283
  """
160
284
  assert isinstance(obj, dict)
161
285
  if "$ref" in obj:
162
286
  reference = obj["$ref"]
163
287
  if isinstance(reference, str) and not reference.startswith("#/"):
164
- referred_file = Path(os.path.abspath(source_path / reference))
288
+ # Split off an optional JSON Pointer fragment (#/...)
289
+ if "#" in reference:
290
+ file_part, fragment = reference.split("#", 1)
291
+ else:
292
+ file_part, fragment = reference, ""
293
+
294
+ referred_file = Path(os.path.abspath(source_path / file_part))
165
295
 
166
296
  if referred_file.exists():
167
297
  logger.debug("Handling $ref source: %s", reference)
168
298
  else:
169
299
  raise OpenAPIFileNotFoundError(reference, referred_file)
300
+
170
301
  sub_fragment = read_from_source(str(referred_file))
302
+
303
+ if fragment:
304
+ # Resolve the JSON Pointer (RFC 6901) into the loaded data.
305
+ # Strip the leading '/' then split on '/'.
306
+ keys = fragment.lstrip("/").split("/")
307
+ for key in keys:
308
+ if (
309
+ not isinstance(sub_fragment, dict)
310
+ or key not in sub_fragment
311
+ ):
312
+ raise OpenAPIDocumentationHandlerError(
313
+ f"Cannot resolve fragment '{fragment}' in {referred_file}: "
314
+ f"key '{key}' not found."
315
+ )
316
+ sub_fragment = sub_fragment[key]
317
+
171
318
  return self._transform_data(sub_fragment, referred_file.parent)
172
319
  else:
173
320
  return obj
@@ -179,7 +326,10 @@ class OpenAPIV3DocumentationHandler:
179
326
  """
180
327
  data = self.doc
181
328
  groups = defaultdict(list)
182
- paths = data["paths"]
329
+ paths = data.get("paths") # paths is optional in OAS 3.1
330
+
331
+ if not paths:
332
+ return groups
183
333
 
184
334
  for path, path_item in paths.items():
185
335
  if not isinstance(path_item, dict):
@@ -457,6 +607,19 @@ class OpenAPIV3DocumentationHandler:
457
607
 
458
608
  return results
459
609
 
610
+ def get_response_headers(self, response_definition: dict) -> dict:
611
+ """
612
+ Returns the headers of a response definition, resolving any $ref values
613
+ so that the template can access fields like schema and description directly.
614
+ """
615
+ headers = response_definition.get("headers")
616
+ if not headers:
617
+ return {}
618
+ return {
619
+ name: self._resolve_opt_ref(header_def)
620
+ for name, header_def in headers.items()
621
+ }
622
+
460
623
  def write(self) -> str:
461
624
  return self._writer.write(
462
625
  self.doc,
@@ -35,6 +35,10 @@ class ScalarExampleHandler(SchemaExampleHandler):
35
35
  formats: Dict[str, Callable[[], Any]]
36
36
 
37
37
  def get_example(self, schema) -> str:
38
+ enum = schema.get("enum")
39
+ if isinstance(enum, list) and enum:
40
+ return enum[0]
41
+
38
42
  format = schema.get("format")
39
43
 
40
44
  if format and format in self.formats:
@@ -56,12 +60,6 @@ class StringExampleHandler(ScalarExampleHandler):
56
60
  "binary": lambda: "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=",
57
61
  }
58
62
 
59
- def get_example(self, schema) -> str:
60
- enum = schema.get("enum")
61
- if isinstance(enum, list):
62
- return enum[0]
63
- return super().get_example(schema)
64
-
65
63
 
66
64
  class IntegerExampleHandler(ScalarExampleHandler):
67
65
  type_name = "integer"
@@ -138,6 +136,10 @@ def get_example_from_schema(schema) -> Any:
138
136
  if "example" in schema:
139
137
  return schema["example"]
140
138
 
139
+ examples = schema.get("examples")
140
+ if isinstance(examples, list) and examples:
141
+ return examples[0]
142
+
141
143
  # does it have a type?
142
144
  handlers_types: List[Type[SchemaExampleHandler]] = list(
143
145
  get_subclasses(SchemaExampleHandler)
@@ -145,6 +147,11 @@ def get_example_from_schema(schema) -> Any:
145
147
 
146
148
  schema_type = schema.get("type")
147
149
 
150
+ # OAS 3.1: type can be a list (e.g. ["string", "null"]). Use the first non-null type.
151
+ if isinstance(schema_type, list):
152
+ non_null = [t for t in schema_type if t != "null"]
153
+ schema_type = non_null[0] if non_null else None
154
+
148
155
  if schema_type:
149
156
  handler_type = next(
150
157
  (_type for _type in handlers_types if _type.type_name == schema_type), None
@@ -4,7 +4,7 @@
4
4
  {%- for name, definition in components.responses.items() %}
5
5
 
6
6
  ### {{name}}
7
- {% if definition.description %}{{definition.description | wordwrap(80)}}{% endif -%}
7
+ {% if definition.description %}{{definition.description}}{% endif -%}
8
8
 
9
9
  {%- if definition.content -%}
10
10
  {% with content = handler.simplify_content(definition.content) -%}
@@ -3,6 +3,6 @@
3
3
  ```json
4
4
  {{handler.write_content_example(example, content_type) | safe}}
5
5
  ```
6
- {% if example.auto_generated %}_{{texts.auto_generated_example_note | wordwrap(80)}}_{% endif -%}
6
+ {% if example.auto_generated %}_{{texts.auto_generated_example_note}}_{% endif -%}
7
7
  {% endif %}
8
8
  {% endfor %}
@@ -1,7 +1,7 @@
1
1
  ## {{texts.external_docs}}
2
2
 
3
3
  {% if externalDocs.description -%}
4
- {{externalDocs.description | wordwrap(80)}}
4
+ {{externalDocs.description}}
5
5
 
6
6
  ---
7
7
  {% endif -%}
@@ -1,7 +1,7 @@
1
1
  # {{info.title}} {{info.version}}
2
2
 
3
3
  {% if info.description -%}
4
- {{info.description | wordwrap(80)}}
4
+ {{info.description}}
5
5
 
6
6
  ---
7
7
  {% endif -%}
@@ -7,13 +7,13 @@
7
7
 
8
8
  ### {{http_method.upper()}} {{path | safe}}
9
9
  {% if "summary" in operation -%}
10
- {{operation.summary | wordwrap(80)}}
10
+ {{operation.summary}}
11
11
  {%- endif -%}
12
12
 
13
13
  {%- if operation.description and operation.summary != operation.description %}
14
14
 
15
15
  **{{texts.description.title()}}**
16
- {{operation.description | wordwrap(80)}}
16
+ {{operation.description}}
17
17
 
18
18
  {%- endif -%}
19
19
 
@@ -0,0 +1,9 @@
1
+
2
+ **{{texts.parameters}}**
3
+
4
+ {% with rows = [[texts.parameter, texts.parameter_location, texts.type, texts.default, texts.nullable, texts.description]] %}
5
+ {%- for param in parameters -%}
6
+ {%- set _ = rows.append([param.name, param.in, get_type_display(read_dict(param, "schema", "type")), read_dict(param, "schema", "default", default=""), texts.get_yes_no(is_nullable_schema(read_dict(param, "schema") or {})), read_dict(param, "description", default="")]) -%}
7
+ {%- endfor -%}
8
+ {{ rows | table }}
9
+ {%- endwith -%}
@@ -25,7 +25,7 @@ Refer to the common response description: [{{type_name}}](#{{type_name.lower()}}
25
25
  {%- if definition.headers %}
26
26
 
27
27
  {% with rows = [[texts.name, texts.description, texts.schema]] %}
28
- {%- for header_name, header_definition in definition.headers.items() -%}
28
+ {%- for header_name, header_definition in handler.get_response_headers(definition).items() -%}
29
29
  {%- set _ = rows.append([header_name, header_definition.description, header_definition.schema.type]) -%}
30
30
  {%- endfor -%}
31
31
  {{ rows | table }}
@@ -5,7 +5,7 @@
5
5
  {%- endif -%}
6
6
 
7
7
  {%- if schema.type -%}
8
- {%- with type_name = schema["type"], nullable = schema.get("nullable") -%}
8
+ {%- with type_name = get_primary_type(schema["type"]), nullable = is_nullable_schema(schema) -%}
9
9
  {%- if type_name == "object" -%}
10
10
  {%- if schema.example -%}
11
11
  _{{texts.example}}: _`{{schema.example}}`
@@ -19,9 +19,7 @@ _{{texts.properties}}: _`{{", ".join(schema.properties.keys())}}`
19
19
  {%- if schema.format -%}
20
20
  ({{schema.format}})
21
21
  {%- endif -%}
22
- {%- if nullable -%}
23
- | null
24
- {%- endif -%}
22
+ {%- if nullable %} | null{%- endif -%}
25
23
  {%- endif -%}
26
24
  {%- if type_name == "array" -%}
27
25
  {%- with schema = schema["items"] -%}
@@ -1,10 +1,10 @@
1
- {% if definition.type == "object" %}
1
+ {% if get_primary_type(definition.type) == "object" %}
2
2
  {%- with props = handler.get_properties(definition) -%}
3
3
  {% if props %}
4
- | {{texts.name}} | {{texts.type}} |
5
- | -- | -- |
4
+ | {{texts.name}} | {{texts.type}} | {{texts.description}} |
5
+ | --- | --- | --- |
6
6
  {%- for name, schema in props %}
7
- | {{name}} | {% include "partial/schema-repr.html" %} |
7
+ | {{name}} | {% include "partial/schema-repr.html" %} | {{schema.description}} |
8
8
  {%- endfor -%}
9
9
  {%- endif %}
10
10
  {%- endwith -%}
@@ -5,7 +5,7 @@
5
5
  {% for name, definition in components.responses.items() %}
6
6
 
7
7
  ### {{name}}
8
- {% if definition.description %}{{definition.description | wordwrap(80)}}{% endif %}
8
+ {% if definition.description %}{{definition.description}}{% endif %}
9
9
 
10
10
  {%- if definition.content %}
11
11
  {% with content = handler.simplify_content(definition.content) %}
@@ -1,7 +1,7 @@
1
1
  ## {{texts.external_docs}}
2
2
 
3
3
  {% if externalDocs.description -%}
4
- {{externalDocs.description | wordwrap(80)}}
4
+ {{externalDocs.description}}
5
5
 
6
6
  <hr />
7
7
  {%- endif -%}
@@ -1,7 +1,7 @@
1
1
  # {{info.title}} <span class="api-version">{{info.version}}</span>
2
2
 
3
3
  {% if info.description -%}
4
- {{info.description | wordwrap(80)}}
4
+ {{info.description}}
5
5
  <hr />
6
6
  {%- endif -%}
7
7
 
@@ -7,13 +7,13 @@
7
7
 
8
8
  ### <span class="http-{{http_method.lower()}}">{{http_method.upper()}}</span> {{path | route | safe}}
9
9
  {% if "summary" in operation -%}
10
- {{operation.summary | wordwrap(80)}}
10
+ {{operation.summary}}
11
11
  {%- endif -%}
12
12
 
13
13
  {%- if operation.description and operation.summary != operation.description %}
14
14
 
15
15
  ??? note "{{texts.description.title()}}"
16
- {{operation.description | wordwrap(76) | indent(4)}}
16
+ {{operation.description | indent(4)}}
17
17
  {% endif %}
18
18
 
19
19
  {%- with parameters = handler.get_parameters(operation) -%}
@@ -17,9 +17,9 @@
17
17
  <tr>
18
18
  <td class="parameter-name"><code>{{param.name}}</code></td>
19
19
  <td>{{param.in}}</td>
20
- <td>{{read_dict(param, "schema", "type")}}</td>
20
+ <td>{{get_type_display(read_dict(param, "schema", "type"))}}</td>
21
21
  <td>{{read_dict(param, "schema", "default", default="")}}</td>
22
- <td>{{texts.get_yes_no(read_dict(param, "schema", "nullable", default=False))}}</td>
22
+ <td>{{texts.get_yes_no(is_nullable_schema(read_dict(param, "schema") or {}))}}</td>
23
23
  <td>{{read_dict(param, "description", default="")}}</td>
24
24
  </tr>
25
25
  {%- endfor %}
@@ -0,0 +1,42 @@
1
+ <p class="responses-title"><strong>{{texts.responses}}</strong></p>
2
+
3
+ {% for code, definition in operation.responses.items() %}
4
+ === "{% if code == "default" %}{{texts.other_responses}}{% else %}{{code}}{% with phrase = get_http_status_phrase(code) %}{% if phrase %} {{ phrase }}{% endif %}{% endwith %}{% endif %}"
5
+ {%- if is_reference(definition) -%}
6
+ {%- with type_name = definition["$ref"].replace("#/components/responses/", "") %}
7
+ <div class="common-response"><p>Refer to the common response description: <a href="#{{type_name.lower() | link}}" class="ref-link">{{type_name}}</a>.</p></div>
8
+ {%- endwith -%}
9
+ {%- endif -%}
10
+ {%- if definition.content %}
11
+ {%- with content = handler.simplify_content(definition.content) %}
12
+ {% for content_type, definition in content.items() %}
13
+ === "{{content_type}}"
14
+ {% for example in handler.get_content_examples(definition) %}
15
+ {% if example.value %}
16
+ ```json
17
+ {{handler.write_content_example(example, content_type) | indent(8) | safe}}
18
+ ```
19
+ {% if example.auto_generated %}<span class="small-note">⚠️</span>&nbsp;<em class="small-note warning">{{texts.auto_generated_example_note}}</em>{% endif -%}
20
+ {% endif %}
21
+ {% endfor %}
22
+ {% if "alt_types" in definition %}<em class="small-note alt-types">{{texts.other_possible_types}}: {{definition.alt_types | join(", ")}}</em>{% endif %}
23
+
24
+ ??? hint "{{texts.schema_of_the_response_body}}"
25
+ ```json
26
+ {{handler.write_content_schema(definition) | indent(12) | safe}}
27
+ ```
28
+ {% endfor %}
29
+ {% endwith -%}
30
+ {% endif -%}
31
+ {%- if definition.headers %}
32
+
33
+ **{{texts.response_headers}}**
34
+
35
+ | {{texts.name}} | {{texts.description}} | {{texts.schema}} |
36
+ | --- | --- | --- |
37
+ {%- for header_name, header_definition in handler.get_response_headers(definition).items() %}
38
+ | `{{header_name}}` | {{header_definition.description or ""}} | {%- with schema = header_definition.schema %}{%- include "partial/schema-repr.html" -%}{%- endwith %} |
39
+ {%- endfor %}
40
+
41
+ {% endif -%}
42
+ {%- endfor -%}
@@ -5,7 +5,7 @@
5
5
  {%- endif -%}
6
6
 
7
7
  {%- if schema.type -%}
8
- {%- with type_name = schema["type"], nullable = schema.get("nullable") -%}
8
+ {%- with type_name = get_primary_type(schema["type"]), nullable = is_nullable_schema(schema) -%}
9
9
  {%- if type_name == "object" -%}
10
10
  {%- if schema.example -%}
11
11
  <em>{{texts.example}}: </em><code>{{schema.example}}</code>
@@ -19,9 +19,7 @@
19
19
  {%- if schema.format -%}
20
20
  (<span class="{{schema.format}}-format format">{{schema.format}}</span>)
21
21
  {%- endif -%}
22
- {%- if nullable -%}
23
- &#124; <span class="null-type">null</span>
24
- {%- endif -%}
22
+ {%- if nullable %} &#124; <span class="null-type">null</span>{%- endif -%}
25
23
  {%- endif -%}
26
24
  {%- if type_name == "array" -%}
27
25
  {%- with schema = schema["items"] -%}
@@ -1,4 +1,4 @@
1
- {% if definition.type == "object" %}
1
+ {% if get_primary_type(definition.type) == "object" %}
2
2
  {%- with props = handler.get_properties(definition) -%}
3
3
  {% if props %}
4
4
  <table>
@@ -6,6 +6,7 @@
6
6
  <tr>
7
7
  <th>{{texts.name}}</th>
8
8
  <th>{{texts.type}}</th>
9
+ <th>{{texts.description}}</th>
9
10
  </tr>
10
11
  </thead>
11
12
  <tbody>
@@ -13,6 +14,7 @@
13
14
  <tr>
14
15
  <td><code>{{name}}</code></td>
15
16
  <td>{% include "partial/schema-repr.html" %}</td>
17
+ <td>{{schema.description}}</td>
16
18
  </tr>
17
19
  {%- endfor %}
18
20
  </tbody>
@@ -5,7 +5,7 @@
5
5
  {%- endif -%}
6
6
 
7
7
  {%- if schema.type -%}
8
- {%- with type_name = schema["type"], nullable = schema.get("nullable") -%}
8
+ {%- with type_name = get_primary_type(schema["type"]), nullable = is_nullable_schema(schema) -%}
9
9
  {# Scalar types #}
10
10
  {%- if type_name in scalar_types -%}
11
11
  {{type_name}}
@@ -5,7 +5,7 @@
5
5
  {%- endif -%}
6
6
 
7
7
  {%- if schema.type -%}
8
- {%- with type_name = schema["type"], nullable = schema.get("nullable") -%}
8
+ {%- with type_name = get_primary_type(schema["type"]), nullable = is_nullable_schema(schema) -%}
9
9
  {# Scalar types #}
10
10
  {%- if type_name in scalar_types -%}
11
11
  {{type_name}}
@@ -350,8 +350,30 @@ class Schema(OpenAPIElement):
350
350
  A list of schemas where at least one must apply.
351
351
  one_of (list["Schema" | "Reference"] | None):
352
352
  A list of schemas where exactly one must apply.
353
- not_ (list["Schema" | "Reference"] | None):
353
+ not_ (None | "Schema" | "Reference"):
354
354
  A schema that must not apply.
355
+ if_ (None | "Schema" | "Reference"):
356
+ If-then-else conditional schema (JSON Schema / OAS 3.1).
357
+ then_ (None | "Schema" | "Reference"):
358
+ Schema applied when if_ validates successfully.
359
+ else_ (None | "Schema" | "Reference"):
360
+ Schema applied when if_ fails to validate.
361
+ exclusive_maximum (float | None):
362
+ OAS 3.1 / JSON Schema: the exclusive upper bound for numeric values.
363
+ exclusive_minimum (float | None):
364
+ OAS 3.1 / JSON Schema: the exclusive lower bound for numeric values.
365
+ multiple_of (float | None):
366
+ The value must be a multiple of this number.
367
+ const (Any | None):
368
+ OAS 3.1 / JSON Schema: the value must be exactly this value.
369
+ prefix_items (list["Schema" | "Reference"] | None):
370
+ OAS 3.1 / JSON Schema: schemas for tuple validation of array items.
371
+ unevaluated_properties (None | bool | "Schema" | "Reference"):
372
+ OAS 3.1 / JSON Schema: controls handling of unevaluated properties.
373
+ unevaluated_items (None | bool | "Schema" | "Reference"):
374
+ OAS 3.1 / JSON Schema: controls handling of unevaluated array items.
375
+ defs (dict[str, "Schema" | "Reference"] | None):
376
+ OAS 3.1 / JSON Schema: locally-scoped schema definitions ($defs).
355
377
  """
356
378
 
357
379
  type: None | str | ValueType | list[None | str | ValueType] = None
@@ -376,15 +398,26 @@ class Schema(OpenAPIElement):
376
398
  unique_items: bool | None = None
377
399
  maximum: float | None = None
378
400
  minimum: float | None = None
401
+ exclusive_maximum: float | None = None
402
+ exclusive_minimum: float | None = None
403
+ multiple_of: float | None = None
379
404
  nullable: bool | None = None
380
405
  xml: XML | None = None
381
406
  items: "None | Schema | Reference" = None
382
- enum: list[str] | None = None
407
+ prefix_items: list["Schema | Reference"] | None = None
408
+ enum: list[Any] | None = None
409
+ const: Any | None = None
383
410
  discriminator: Discriminator | None = None
384
411
  all_of: list["Schema | Reference"] | None = None
385
412
  any_of: list["Schema | Reference"] | None = None
386
413
  one_of: list["Schema | Reference"] | None = None
387
- not_: list["Schema | Reference"] | None = None
414
+ not_: "None | Schema | Reference" = None
415
+ if_: "None | Schema | Reference" = None
416
+ then_: "None | Schema | Reference" = None
417
+ else_: "None | Schema | Reference" = None
418
+ unevaluated_properties: "None | bool | Schema | Reference" = None
419
+ unevaluated_items: "None | bool | Schema | Reference" = None
420
+ defs: dict[str, "Schema | Reference"] | None = None
388
421
 
389
422
 
390
423
  @dataclass
@@ -924,8 +957,8 @@ class OpenAPI(OpenAPIRoot):
924
957
  Attributes:
925
958
  openapi (str): The semantic version number of the OpenAPI Specification version.
926
959
  info (Info | None): Metadata about the API.
927
- json_schema_dialect (str): The default value for the $schema keyword within Schema Objects contained
928
- within this OAS document.
960
+ json_schema_dialect (str | None): The default value for the $schema keyword within Schema Objects contained
961
+ within this OAS document. Optional; if omitted, the dialect is not declared.
929
962
  paths (dict[str, PathItem] | None): The available paths and operations for the API.
930
963
  servers (list[Server] | None): An array of Server Objects that provide connectivity information
931
964
  to a target server.
@@ -938,7 +971,7 @@ class OpenAPI(OpenAPIRoot):
938
971
 
939
972
  openapi: str = "3.1.0"
940
973
  info: Info | None = None
941
- json_schema_dialect: str = "https://json-schema.org/draft/2020-12/schema"
974
+ json_schema_dialect: str | None = None
942
975
  paths: dict[str, PathItem] | None = None
943
976
  servers: list[Server] | None = None
944
977
  components: Components | None = None
@@ -1,9 +0,0 @@
1
-
2
- **{{texts.parameters}}**
3
-
4
- {% with rows = [[texts.parameter, texts.parameter_location, texts.type, texts.default, texts.nullable, texts.description]] %}
5
- {%- for param in parameters -%}
6
- {%- set _ = rows.append([param.name, param.in, read_dict(param, "schema", "type"), read_dict(param, "schema", "default", default=""), texts.get_yes_no(read_dict(param, "schema", "nullable", default=False)), read_dict(param, "description", default="")]) -%}
7
- {%- endfor -%}
8
- {{ rows | table }}
9
- {%- endwith -%}
@@ -1,63 +0,0 @@
1
- {% for code, definition in operation.responses.items() %}
2
-
3
- <p class="response-title">
4
- {% if code == "default" -%}
5
- <strong>{{texts.other_responses}}</strong>
6
- {%- else -%}
7
- <strong>Response <span class="response-code code-{{code}}">{{code}}</span>
8
- {%- with phrase = get_http_status_phrase(code) -%}
9
- {%- if phrase -%}
10
- &nbsp;<span class="status-phrase">{{ phrase }}</span>
11
- {%- endif -%}
12
- {%- endwith -%}
13
- </strong>
14
- {%- endif %}
15
- </p>
16
- {%- if is_reference(definition) -%}
17
- {%- with type_name = definition["$ref"].replace("#/components/responses/", "") %}
18
- <div class="common-response"><p>Refer to the common response description: <a href="#{{type_name.lower() | link}}" class="ref-link">{{type_name}}</a>.</p></div>
19
- {%- endwith -%}
20
- {%- endif -%}
21
- {%- if definition.content %}
22
- {%- with content = handler.simplify_content(definition.content) %}
23
- {% for content_type, definition in content.items() %}
24
- === "{{content_type}}"
25
- {% include "partial/content-examples.html" %}
26
- {% if "alt_types" in definition %}<em class="small-note alt-types">{{texts.other_possible_types}}: {{definition.alt_types | join(", ")}}</em>{% endif %}
27
-
28
- ??? hint "{{texts.schema_of_the_response_body}}"
29
- ```json
30
- {{handler.write_content_schema(definition) | indent(8) | safe}}
31
- ```
32
- {% endfor %}
33
- {% endwith -%}
34
- {% endif -%}
35
- {%- if definition.headers %}
36
- <div class="response-section">
37
- <p class="response-headers sub-section-title">{{texts.response_headers}}</p>
38
-
39
- <table>
40
- <thead>
41
- <tr>
42
- <th>{{texts.name}}</th>
43
- <th>{{texts.description}}</th>
44
- <th>{{texts.schema}}</th>
45
- </tr>
46
- </thead>
47
- <tbody>
48
- {%- for header_name, header_definition in definition.headers.items() %}
49
- <tr>
50
- <td><code>{{header_name}}</code></td>
51
- <td>{{header_definition.description}}</td>
52
- <td>
53
- {%- with schema = header_definition.schema %}
54
- {%- include "partial/schema-repr.html" -%}
55
- {% endwith -%}
56
- </td>
57
- </tr>
58
- {%- endfor %}
59
- </tbody>
60
- </table>
61
- </div>
62
- {% endif -%}
63
- {%- endfor -%}