Skip to content

Commit 53ed97e

Browse files
committed
loader: support json and yaml separately
json is not exactly a subset of yaml, as yaml do not support tabs there are maybe other subtleties, so we just use content-type to figure out how to parse Signed-off-by: Pierre Tardy <[email protected]>
1 parent f0bb516 commit 53ed97e

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

openapi_python_client/__init__.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
""" Generate modern Python clients from OpenAPI """
22

3+
import json
4+
import mimetypes
35
import shutil
46
import subprocess
57
import sys
@@ -361,21 +363,40 @@ def update_existing_client(
361363
return project.update()
362364

363365

366+
def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[str, Any], GeneratorError]:
367+
if content_type == "application/json":
368+
try:
369+
return json.loads(data.decode())
370+
except ValueError as err:
371+
return GeneratorError(header="Invalid JSON from provided source: {}".format(str(err)))
372+
else:
373+
try:
374+
return yaml.safe_load(data)
375+
except yaml.YAMLError as err:
376+
return GeneratorError(header="Invalid YAML from provided source: {}".format(str(err)))
377+
378+
364379
def _get_document(*, url: Optional[str], path: Optional[Path]) -> Union[Dict[str, Any], GeneratorError]:
365380
yaml_bytes: bytes
381+
content_type: Optional[str]
366382
if url is not None and path is not None:
367383
return GeneratorError(header="Provide URL or Path, not both.")
368384
if url is not None:
369385
try:
370386
response = httpx.get(url)
371387
yaml_bytes = response.content
388+
if "content-type" in response.headers:
389+
content_type = response.headers["content-type"].split(";")[0]
390+
else:
391+
content_type = mimetypes.guess_type(url, strict=True)[0]
392+
372393
except (httpx.HTTPError, httpcore.NetworkError):
373394
return GeneratorError(header="Could not get OpenAPI document from provided URL")
374395
elif path is not None:
375396
yaml_bytes = path.read_bytes()
397+
content_type = mimetypes.guess_type(path.as_uri(), strict=True)[0]
398+
376399
else:
377400
return GeneratorError(header="No URL or Path provided")
378-
try:
379-
return yaml.safe_load(yaml_bytes)
380-
except yaml.YAMLError:
381-
return GeneratorError(header="Invalid YAML from provided source")
401+
402+
return _load_yaml_or_json(yaml_bytes, content_type)

tests/test___init__.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def test__get_document_path_no_url(self, mocker):
218218

219219
def test__get_document_bad_yaml(self, mocker):
220220
get = mocker.patch("httpx.get")
221-
loads = mocker.patch("yaml.safe_load", side_effect=yaml.YAMLError)
221+
loads = mocker.patch("yaml.safe_load", side_effect=yaml.YAMLError("error line 2"))
222222

223223
from openapi_python_client import _get_document
224224

@@ -228,7 +228,44 @@ def test__get_document_bad_yaml(self, mocker):
228228
get.assert_not_called()
229229
path.read_bytes.assert_called_once()
230230
loads.assert_called_once_with(path.read_bytes())
231-
assert result == GeneratorError(header="Invalid YAML from provided source")
231+
assert result == GeneratorError(header="Invalid YAML from provided source: error line 2")
232+
233+
def test__get_document_json(self, mocker):
234+
class FakeResponse:
235+
content = b'{\n\t"foo": "bar"}'
236+
headers = {"content-type": "application/json; encoding=utf8"}
237+
238+
get = mocker.patch("httpx.get", return_value=FakeResponse())
239+
yaml_loads = mocker.patch("yaml.safe_load")
240+
json_result = mocker.MagicMock()
241+
json_loads = mocker.patch("json.loads", return_value=json_result)
242+
243+
from openapi_python_client import _get_document
244+
245+
url = mocker.MagicMock()
246+
result = _get_document(url=url, path=None)
247+
248+
get.assert_called_once()
249+
json_loads.assert_called_once_with(FakeResponse.content.decode())
250+
assert result == json_result
251+
252+
def test__get_document_bad_json(self, mocker):
253+
class FakeResponse:
254+
content = b'{"foo"}'
255+
headers = {"content-type": "application/json; encoding=utf8"}
256+
257+
get = mocker.patch("httpx.get", return_value=FakeResponse())
258+
json_result = mocker.MagicMock()
259+
260+
from openapi_python_client import _get_document
261+
262+
url = mocker.MagicMock()
263+
result = _get_document(url=url, path=None)
264+
265+
get.assert_called_once()
266+
assert result == GeneratorError(
267+
header="Invalid JSON from provided source: " "Expecting ':' delimiter: line 1 column 7 (char 6)"
268+
)
232269

233270

234271
def make_project(**kwargs):

0 commit comments

Comments
 (0)