Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: #182. Fix yaml support. Improve OAS3 #183

Merged
merged 1 commit into from Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 1 addition & 9 deletions datamodel_code_generator/__init__.py
Expand Up @@ -74,14 +74,6 @@ def inner(cls: Type[T]) -> Type[T]:
return inner


def load_json_or_yaml(text: str) -> Dict[Any, Any]:
try:
data = json.loads(text)
except JSONDecodeError:
data = yaml.safe_load(text)
return data


@contextlib.contextmanager
def chdir(path: Optional[Path]) -> Iterator[None]:
"""Changes working directory and returns to previous on exit."""
Expand All @@ -98,7 +90,7 @@ def chdir(path: Optional[Path]) -> Iterator[None]:


def is_openapi(text: str) -> bool:
return 'openapi' in load_json_or_yaml(text)
return 'openapi' in yaml.safe_load(text)


class InputFileType(Enum):
Expand Down
5 changes: 2 additions & 3 deletions datamodel_code_generator/parser/jsonschema.py
Expand Up @@ -14,6 +14,7 @@
Union,
)

import yaml
from pydantic import BaseModel, Field, validator

from datamodel_code_generator import snooper_to_methods
Expand Down Expand Up @@ -560,8 +561,6 @@ def parse_ref(self, obj: JsonSchemaObject, path: List[str]) -> None:
# Local Reference – $ref: '#/definitions/myElement'
pass
else:
import yaml

relative_path, object_path = obj.ref.split('#/')
remote_object: Optional[
Dict[str, Any]
Expand Down Expand Up @@ -633,7 +632,7 @@ def parse_raw_obj(self, name: str, raw: Dict[str, Any], path: List[str]) -> None
self.parse_ref(obj, path)

def parse_raw(self) -> None:
raw_obj: Dict[Any, Any] = json.loads(self.text) # type: ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always use yaml parser so we don't care about file format.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great 👍

raw_obj: Dict[Any, Any] = yaml.safe_load(self.text) # type: ignore
obj_name = raw_obj.get('title', 'Model')
obj_name = self.model_resolver.add([], obj_name, unique=False).name
self.parse_raw_obj(obj_name, raw_obj, [])
Expand Down
6 changes: 4 additions & 2 deletions datamodel_code_generator/parser/openapi.py
@@ -1,6 +1,8 @@
from typing import Any, Dict

from datamodel_code_generator import load_json_or_yaml, snooper_to_methods
import yaml

from datamodel_code_generator import snooper_to_methods
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser


Expand All @@ -15,7 +17,7 @@ def parse_raw(self) -> None:
)
components: Dict[str, Any] = base_parser.specification['components']
else:
components = load_json_or_yaml(self.text)['components'] # type: ignore
components = yaml.safe_load(self.text)['components'] # type: ignore

for obj_name, raw_obj in components[
'schemas'
Expand Down
@@ -0,0 +1,13 @@
from __future__ import annotations

from typing import Optional

from pydantic import AnyUrl, BaseModel, conint


class Problem(BaseModel):
detail: Optional[str] = None
instance: Optional[AnyUrl] = None
status: Optional[conint(ge=100, le=600, lt=1)] = None
title: Optional[str] = None
type: Optional[AnyUrl] = 'about:blank'
8 changes: 8 additions & 0 deletions tests/data/jsonschema/nested_array.json.snapshot
@@ -0,0 +1,8 @@
class BoundingBox(BaseModel):
type: str
coordinates: List[Union[float, str]]


class Model(BaseModel):
bounding_box: Optional[BoundingBox] = None
attributes: Optional[Dict[str, Any]] = None
20 changes: 20 additions & 0 deletions tests/data/jsonschema/oneof.json
@@ -0,0 +1,20 @@
{
"properties": {
"item": {
"properties": {
"timeout": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
}
},
"type": "object"
}
}
}

6 changes: 6 additions & 0 deletions tests/data/jsonschema/oneof.json.snapshot
@@ -0,0 +1,6 @@
class Item(BaseModel):
timeout: Optional[Union[str, int]] = None


class OnOfObject(BaseModel):
item: Optional[Item] = None
42 changes: 42 additions & 0 deletions tests/data/openapi/definitions.yaml
@@ -0,0 +1,42 @@
schemas:
Problem:
properties:
detail:
description: |
A human readable explanation specific to this occurrence of the
problem. You MUST NOT expose internal informations, personal
data or implementation details through this field.
example: Request took too long to complete.
type: string
instance:
description: |
An absolute URI that identifies the specific occurrence of the problem.
It may or may not yield further information if dereferenced.
format: uri
type: string
status:
description: |
The HTTP status code generated by the origin server for this occurrence
of the problem.
example: 503
exclusiveMaximum: true
format: int32
maximum: 600
minimum: 100
type: integer
title:
description: |
A short, summary of the problem type. Written in english and readable
for engineers (usually not suited for non technical stakeholders and
not localized); example: Service Unavailable
type: string
type:
default: about:blank
description: |
An absolute URI that identifies the problem type. When dereferenced,
it SHOULD provide human-readable documentation for the problem type
(e.g., using HTML).
example: https://tools.ietf.org/html/rfc7231#section-6.6.4
format: uri
type: string
type: object
5 changes: 5 additions & 0 deletions tests/data/openapi/refs.yaml
@@ -0,0 +1,5 @@
openapi: 3.0.1
components:
schemas:
Problem:
$ref: "https://teamdigitale.github.io/openapi/0.0.6/definitions.yaml#/schemas/Problem"
30 changes: 3 additions & 27 deletions tests/parser/test_jsonschema.py
Expand Up @@ -260,24 +260,8 @@ def test_parse_any_root_object(source_obj, generated_classes):
'source_obj,generated_classes',
[
(
{
"properties": {
"item": {
"properties": {
"timeout": {
"oneOf": [{"type": "string"}, {"type": "integer"}]
}
},
"type": "object",
}
}
},
"""class Item(BaseModel):
timeout: Optional[Union[str, int]] = None


class OnOfObject(BaseModel):
item: Optional[Item] = None""",
yaml.safe_load((DATA_PATH / 'oneof.json').read_text()),
(DATA_PATH / 'oneof.json.snapshot').read_text(),
)
],
)
Expand Down Expand Up @@ -345,13 +329,5 @@ def test_parse_nested_array():
parser.parse()
assert (
dump_templates(list(parser.results))
== """\
class BoundingBox(BaseModel):
type: str
coordinates: List[Union[float, str]]


class Model(BaseModel):
bounding_box: Optional[BoundingBox] = None
attributes: Optional[Dict[str, Any]] = None"""
== (DATA_PATH / 'nested_array.json.snapshot').read_text()
)
20 changes: 20 additions & 0 deletions tests/parser/test_openapi.py
Expand Up @@ -527,3 +527,23 @@ def test_openapi_parser_parse_array_enum(with_import, format_, base_class):
parser.parse(with_import=with_import, format_=format_)
== expected_file.read_text()
)


@pytest.mark.parametrize(
'with_import, format_, base_class', [(True, True, None,),],
)
def test_openapi_parser_parse_remote_ref(with_import, format_, base_class):
parser = OpenAPIParser(
BaseModel,
CustomRootType,
base_class=base_class,
text=(DATA_PATH / 'refs.yaml').read_text(),
)
expected_file = get_expected_file(
'openapi_parser_parse_remote_ref', with_import, format_, base_class
)

assert (
parser.parse(with_import=with_import, format_=format_)
== expected_file.read_text()
)