Skip to content

Commit 5e275f9

Browse files
committed
Add support for alternative JSON decoders
1 parent e3f907b commit 5e275f9

File tree

8 files changed

+52
-3
lines changed

8 files changed

+52
-3
lines changed

openapi_python_client/config.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import mimetypes
33
from enum import Enum
44
from pathlib import Path
5-
from typing import Optional, Union
5+
from typing import Literal, Optional, Union
66

77
from attr import define
88
from pydantic import BaseModel
@@ -28,6 +28,9 @@ class MetaType(str, Enum):
2828
PDM = "pdm"
2929
UV = "uv"
3030

31+
class JSONDecoder(str, Enum):
32+
UJSON = "ujson"
33+
ORJSON = "orjson"
3134

3235
class ConfigFile(BaseModel):
3336
"""Contains any configurable values passed via a config file.
@@ -47,6 +50,7 @@ class ConfigFile(BaseModel):
4750
generate_all_tags: bool = False
4851
http_timeout: int = 5
4952
literal_enums: bool = False
53+
alt_json_decoder: Optional[str] = None
5054

5155
@staticmethod
5256
def load_from_path(path: Path) -> "ConfigFile":
@@ -82,6 +86,7 @@ class Config:
8286
content_type_overrides: dict[str, str]
8387
overwrite: bool
8488
output_path: Optional[Path]
89+
alt_json_decoder: Optional[JSONDecoder]
8590

8691
@staticmethod
8792
def from_sources(
@@ -104,6 +109,14 @@ def from_sources(
104109
"ruff check --fix .",
105110
"ruff format .",
106111
]
112+
113+
if config_file.alt_json_decoder == "ujson":
114+
json_decoder = JSONDecoder.UJSON
115+
elif config_file.alt_json_decoder == "orjson":
116+
json_decoder = JSONDecoder.ORJSON
117+
else:
118+
json_decoder = None
119+
107120

108121
config = Config(
109122
meta_type=meta_type,
@@ -119,6 +132,7 @@ def from_sources(
119132
generate_all_tags=config_file.generate_all_tags,
120133
http_timeout=config_file.http_timeout,
121134
literal_enums=config_file.literal_enums,
135+
alt_json_decoder=json_decoder,
122136
document_source=document_source,
123137
file_encoding=file_encoding,
124138
overwrite=overwrite,

openapi_python_client/parser/responses.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class _ResponseSource(TypedDict):
3737

3838

3939
JSON_SOURCE = _ResponseSource(attribute="response.json()", return_type="Any")
40+
ALT_JSON_SOURCE = _ResponseSource(attribute="loads(response.content)", return_type="Any")
4041
BYTES_SOURCE = _ResponseSource(attribute="response.content", return_type="bytes")
4142
TEXT_SOURCE = _ResponseSource(attribute="response.text", return_type="str")
4243
NONE_SOURCE = _ResponseSource(attribute="None", return_type="None")
@@ -135,15 +136,20 @@ def _source_by_content_type(content_type: str, config: Config) -> Optional[_Resp
135136

136137
if parsed_content_type.startswith("text/"):
137138
return TEXT_SOURCE
139+
140+
if config.alt_json_decoder:
141+
USED_JSON_SOURCE = ALT_JSON_SOURCE
142+
else:
143+
USED_JSON_SOURCE = JSON_SOURCE
138144

139145
known_content_types = {
140-
"application/json": JSON_SOURCE,
146+
"application/json": USED_JSON_SOURCE,
141147
"application/octet-stream": BYTES_SOURCE,
142148
}
143149
source = known_content_types.get(parsed_content_type)
144150
if source is None and parsed_content_type.endswith("+json"):
145151
# Implements https://www.rfc-editor.org/rfc/rfc6838#section-4.2.8 for the +json suffix
146-
source = JSON_SOURCE
152+
source = USED_JSON_SOURCE
147153
return source
148154

149155

openapi_python_client/templates/endpoint_module.py.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ from http import HTTPStatus
22
from typing import Any, Optional, Union, cast
33

44
import httpx
5+
{% if endpoint.json_decoder %}
6+
from {{ endpoint.json_decoder }} import loads
7+
{% endif %}
58

69
from ...client import AuthenticatedClient, Client
710
from ...types import Response, UNSET
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% macro json_package(config) %}
2+
{% if config.alt_json_decoder == "ujson" %}
3+
ujson
4+
{% elif config.alt_json_decoder == "orjson" %}
5+
orjson
6+
{% endif %}
7+
{% endmacro %}
8+
9+
{% macro json_package_ver(config) %}
10+
{% if config.alt_json_decoder == "ujson" %}
11+
>=5.11.0
12+
{% elif config.alt_json_decoder == "orjson" %}
13+
>=3.11.3
14+
{% endif %}
15+
{% endmacro %}

openapi_python_client/templates/pyproject_pdm.toml.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ dependencies = [
99
"httpx>=0.23.0,<0.29.0",
1010
"attrs>=22.2.0",
1111
"python-dateutil>=2.8.0",
12+
{% if config.alt_json_decoder %}
13+
"{{ json_package(config) }}{{ json_package_vers(config) }}",
14+
{% endif %}
1215
]
1316

1417
[tool.pdm]

openapi_python_client/templates/pyproject_poetry.toml.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ python = "^3.9"
1414
httpx = ">=0.23.0,<0.29.0"
1515
attrs = ">=22.2.0"
1616
python-dateutil = "^2.8.0"
17+
{% if config.alt_json_decoder %}
18+
{{ json_package(config) }} = "{{ json_package_vers(config) }}"
19+
{% endif %}
1720

1821
[build-system]
1922
requires = ["poetry-core>=2.0.0,<3.0.0"]

openapi_python_client/templates/pyproject_uv.toml.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ dependencies = [
99
"httpx>=0.23.0,<0.29.0",
1010
"attrs>=22.2.0",
1111
"python-dateutil>=2.8.0,<3",
12+
{% if config.alt_json_decoder %}
13+
"{{ json_package(config) }}{{ json_package_vers(config) }}",
14+
{% endif %}
1215
]
1316
1417
[tool.uv.build-backend]

tests/test_parser/test_responses.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def test_response_from_data_property(mocker, any_property_factory):
128128
)
129129
config = MagicMock()
130130
config.content_type_overrides = {}
131+
config.alt_json_decoder = None
131132
status_code = HTTPStatusPattern(pattern="400", code_range=(400, 400))
132133

133134
response, schemas = responses.response_from_data(
@@ -164,6 +165,7 @@ def test_response_from_data_reference(mocker, any_property_factory):
164165
)
165166
config = MagicMock()
166167
config.content_type_overrides = {}
168+
config.alt_json_decoder = None
167169

168170
response, schemas = responses.response_from_data(
169171
status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)),

0 commit comments

Comments
 (0)